OSDN Git Service

シフトキーを押しながらホイールで戦況を横スクロールさせる
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / QuestInfo.cs
1 // Copyright (C) 2013, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Drawing;\r
18 using System.Linq;\r
19 using System.Windows.Forms;\r
20 using System.Xml.Serialization;\r
21 using static System.Math;\r
22 \r
23 namespace KancolleSniffer\r
24 {\r
25     public class QuestStatus\r
26     {\r
27         public int Id { get; set; }\r
28         public int Category { get; set; }\r
29         public string Name { get; set; }\r
30         public string Detail { get; set; }\r
31         public int[] Material { get; set; }\r
32         public int Progress { get; set; }\r
33 \r
34         [XmlIgnore]\r
35         public QuestCount Count { get; set; }\r
36 \r
37         [XmlIgnore]\r
38         public Color Color { get; set; }\r
39 \r
40         public string ToToolTip() =>\r
41             Detail +\r
42             (Material == null || Material.All(x => x == 0)\r
43                 ? ""\r
44                 : "\r\n" + string.Join(" ",\r
45                       new[] {"燃", "弾", "鋼", "ボ", "建造", "修復", "開発", "改修"}\r
46                           .Zip(Material, (m, num) => num == 0 ? "" : m + num)\r
47                           .Where(s => !string.IsNullOrEmpty(s))));\r
48     }\r
49 \r
50     public enum QuestInterval\r
51     {\r
52         Other,\r
53         Daily,\r
54         Weekly,\r
55         Monthly,\r
56         Quarterly\r
57     }\r
58 \r
59     public class QuestSpec\r
60     {\r
61         public QuestInterval Interval { get; set; }\r
62         public int Max { get; set; }\r
63         public int[] MaxArray { get; set; }\r
64         public bool AdjustCount { get; set; } = true;\r
65         public int Shift { get; set; }\r
66         public int[] Material { get; set; }\r
67     }\r
68 \r
69     public class QuestSortie : QuestSpec\r
70     {\r
71         public string Rank { get; set; }\r
72         public int[] Maps { get; set; }\r
73         public int[] ShipTypes { get; set; }\r
74 \r
75         public static int CompareRank(string a, string b)\r
76         {\r
77             const string ranks = "SABCDE";\r
78             return ranks.IndexOf(a, StringComparison.Ordinal) -\r
79                    ranks.IndexOf(b, StringComparison.Ordinal);\r
80         }\r
81 \r
82         public bool Check(string rank, int map, bool boss)\r
83         {\r
84             return (Rank == null || CompareRank(rank, Rank) <= 0) &&\r
85                    (Maps == null || Maps.Contains(map) && boss);\r
86         }\r
87     }\r
88 \r
89     public class QuestEnemyType : QuestSpec\r
90     {\r
91         public int[] EnemyType { get; set; } = new int[0];\r
92 \r
93         public int CountResult(IEnumerable<ShipStatus> enemyResult) =>\r
94             enemyResult.Count(ship => ship.NowHp == 0 && EnemyType.Contains(ship.Spec.ShipType));\r
95     }\r
96 \r
97     public class QuestPractice : QuestSpec\r
98     {\r
99         public bool Win { get; set; }\r
100         public bool Check(string rank) => !Win || QuestSortie.CompareRank(rank, "B") <= 0;\r
101     }\r
102 \r
103     public class QuestMission : QuestSpec\r
104     {\r
105         public int[] Ids { get; set; }\r
106         public bool Check(int id) => Ids == null || Ids.Contains(id);\r
107     }\r
108 \r
109     public class QuestDestroyItem : QuestSpec\r
110     {\r
111         public int[] Items { get; set; }\r
112         public bool Check(int id) => Items == null || Items.Contains(id);\r
113     }\r
114 \r
115     public class QuestPowerup : QuestSpec\r
116     {\r
117     }\r
118 \r
119     public class QuestCount\r
120     {\r
121         public int Id { get; set; }\r
122         public int Now { get; set; }\r
123         public int[] NowArray { get; set; }\r
124 \r
125         [XmlIgnore]\r
126         public QuestSpec Spec { get; set; }\r
127 \r
128         public bool AdjustCount(int progress)\r
129         {\r
130             if (!Spec.AdjustCount)\r
131                 return false;\r
132             if (NowArray != null)\r
133             {\r
134                 if (progress != 100)\r
135                     return false;\r
136                 NowArray = NowArray.Zip(Spec.MaxArray, Max).ToArray();\r
137                 return true;\r
138             }\r
139             var next = 0;\r
140             switch (progress)\r
141             {\r
142                 case 0:\r
143                     next = 50;\r
144                     break;\r
145                 case 50:\r
146                     next = 80;\r
147                     break;\r
148                 case 80:\r
149                     next = 100;\r
150                     break;\r
151                 case 100:\r
152                     next = 100000;\r
153                     break;\r
154             }\r
155             var now = Now + Spec.Shift;\r
156             var max = Spec.Max + Spec.Shift;\r
157             var low = (int)Ceiling(max * progress / 100.0);\r
158             if (low >= max && progress != 100)\r
159                 low = max - 1;\r
160             var high = (int)Ceiling(max * next / 100.0);\r
161             if (now < low)\r
162             {\r
163                 Now = low - Spec.Shift;\r
164                 return true;\r
165             }\r
166             if (now >= high)\r
167             {\r
168                 Now = high - 1 - Spec.Shift;\r
169                 return true;\r
170             }\r
171             return false;\r
172         }\r
173 \r
174         public override string ToString()\r
175         {\r
176             if (Id == 426 || Id == 854 || Id == 873)\r
177                 return $"{NowArray.Count(n => n >= 1)}/{Spec.MaxArray.Length}";\r
178             return NowArray != null\r
179                 ? string.Join(" ", NowArray.Zip(Spec.MaxArray, (n, m) => $"{n}/{m}"))\r
180                 : $"{Now}/{Spec.Max}";\r
181         }\r
182 \r
183         public string ToToolTip()\r
184         {\r
185             switch (Id)\r
186             {\r
187                 case 426:\r
188                     return string.Join(" ",\r
189                         new[] {"警備任務", "対潜警戒任務", "海上護衛任務", "強硬偵察任務"}\r
190                             .Zip(NowArray, (mission, n) => n >= 1 ? mission : "")\r
191                             .Where(s => !string.IsNullOrEmpty(s)));\r
192                 case 428:\r
193                     return string.Join(" ",\r
194                         new[] {"対潜警戒任務", "海峡警備行動", "長時間対潜警戒"}.Zip(NowArray, (mission, n) => n >= 1 ? mission + n : "")\r
195                             .Where(s => !string.IsNullOrEmpty(s)));\r
196                 case 854:\r
197                     return string.Join(" ",\r
198                         new[] {"2-4", "6-1", "6-3", "6-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
199                             .Where(s => !string.IsNullOrEmpty(s)));\r
200                 case 873:\r
201                     return string.Join(" ",\r
202                         new[] {"3-1", "3-2", "3-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
203                             .Where(s => !string.IsNullOrEmpty(s)));\r
204             }\r
205             return "";\r
206         }\r
207 \r
208         public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ?? Now >= Spec.Max;\r
209     }\r
210 \r
211     // @formatter:off\r
212     public class QuestCountList\r
213     {\r
214         private const QuestInterval Daily = QuestInterval.Daily;\r
215         private const QuestInterval Weekly = QuestInterval.Weekly;\r
216         private const QuestInterval Monthly = QuestInterval.Monthly;\r
217         private const QuestInterval Quarterly = QuestInterval.Quarterly;\r
218 \r
219         /// <summary>\r
220         /// このテーブルは七四式電子観測儀を参考に作成した。\r
221         /// https://github.com/andanteyk/ElectronicObserver/blob/develop/ElectronicObserver/Data/Quest/QuestProgressManager.cs\r
222         /// </summary>\r
223         private static readonly Dictionary<int, QuestSpec> QuestSpecs = new Dictionary<int, QuestSpec>\r
224         {\r
225             {201, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 0, 1, 0}}}, // 201: 敵艦隊を撃滅せよ!\r
226             {216, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 1, 1, 0}}}, // 216: 敵艦隊主力を撃滅せよ!\r
227             {210, new QuestSortie {Interval = Daily, Max = 10, Material = new[] {0, 0, 1, 0}}}, // 210: 敵艦隊を10回邀撃せよ!\r
228             {211, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {7, 11}, Material = new[] {0, 2, 0, 0}}}, // 211: 敵空母を3隻撃沈せよ!\r
229             {212, new QuestEnemyType {Interval = Daily, Max = 5, EnemyType = new[] {15}, Material = new[] {0, 0, 2, 0}}}, // 212: 敵輸送船団を叩け!\r
230             {218, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {15}, Material = new[] {0, 1, 1, 0}}}, // 218: 敵補給艦を3隻撃沈せよ!\r
231             {226, new QuestSortie {Interval = Daily, Max = 5, Rank = "B", Maps = new[] {21, 22, 23, 24, 25}, Material = new[] {1, 1, 0, 0}}}, // 226: 南西諸島海域の制海権を握れ!\r
232             {230, new QuestEnemyType {Interval = Daily, Max = 6, EnemyType = new[] {13}, Material = new[] {0, 1, 0, 0}}}, // 230: 敵潜水艦を制圧せよ!\r
233 \r
234             {213, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {15}, Material = new[] {0, 0, 3, 0}}}, // 213: 海上通商破壊作戦\r
235             {214, new QuestSpec {Interval = Weekly, MaxArray = new[] {36, 6, 24, 12}, Material = new[] {2, 0, 2, 0}}}, // 214: あ号作戦\r
236             {220, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {7, 11}, Material = new[] {0, 2, 0, 0}}}, // 220: い号作戦\r
237             {221, new QuestEnemyType {Interval = Weekly, Max = 50, EnemyType = new[] {15}, Material = new[] {0, 3, 0, 0}}}, // 221: ろ号作戦\r
238             {228, new QuestEnemyType {Interval = Weekly, Max = 15, EnemyType = new[] {13}, Material = new[] {0, 2, 0, 1}}}, // 228: 海上護衛戦\r
239             {229, new QuestSortie {Interval = Weekly, Max = 12, Rank = "B", Maps = new[] {41, 42, 43, 44, 45}, Material = new[] {0, 0, 2, 0}}}, // 229: 敵東方艦隊を撃滅せよ!\r
240             {241, new QuestSortie {Interval = Weekly, Max = 5, Rank = "B", Maps = new[] {33, 34, 35}, Material = new[] {0, 0, 3, 3}}}, // 241: 敵北方艦隊主力を撃滅せよ!\r
241             {242, new QuestSortie {Interval = Weekly, Max = 1, Rank = "B", Maps = new[] {44}, Material = new[] {0, 1, 1, 0}}}, // 242: 敵東方中枢艦隊を撃破せよ!\r
242             {243, new QuestSortie {Interval = Weekly, Max = 2, Rank = "S", Maps = new[] {52}, Material = new[] {0, 0, 2, 2}}}, // 243: 南方海域珊瑚諸島沖の制空権を握れ!\r
243             {249, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 5, 0}}}, // 249: 「第五戦隊」出撃せよ!\r
244             {256, new QuestSortie {Interval = Monthly, Max = 3, Rank = "S", Maps = new[] {61}, Material = new[] {0, 0, 0, 0}}}, // 256: 「潜水艦隊」出撃せよ!\r
245             {257, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 0, 3}}}, // 257: 「水雷戦隊」南西へ!\r
246             {259, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 3, 0, 4}}}, // 259: 「水上打撃部隊」南方へ!\r
247             {261, new QuestSortie {Interval = Weekly, Max = 3, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 0, 3}}}, // 261: 海上輸送路の安全確保に努めよ!\r
248             {265, new QuestSortie {Interval = Monthly, Max = 10, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 5, 3}}}, // 265: 海上護衛強化月間\r
249             {266, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 4, 2}}}, // 266: 「水上反撃部隊」突入せよ!\r
250 \r
251             {822, new QuestSortie {Interval = Quarterly, Max = 2, Rank = "S", Maps = new[] {24}, Material = new[] {0, 0, 0, 5}}}, // 822: 沖ノ島海域迎撃戦\r
252             {854, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 0, 4}}}, // 854: 戦果拡張任務!「Z作戦」前段作戦\r
253             {861, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 4, 0, 0}}}, // 861: 強行輸送艦隊、抜錨!\r
254             {862, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 8, 4}}}, // 862: 前線の航空偵察を実施せよ!\r
255             {873, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1}, Material = new[] {0, 0, 0, 0}}}, // 873: 北方海域警備を実施せよ!\r
256             {875, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 0, 0}}}, // 875: 精鋭「三一駆」、鉄底海域に突入せよ!\r
257 \r
258             {303, new QuestPractice {Interval = Daily, Max = 3, Win = false, Material = new[] {1, 0, 0, 0}}}, // 303: 「演習」で練度向上!\r
259             {304, new QuestPractice {Interval = Daily, Max = 5, Win = true, Material = new[] {0, 0, 1, 0}}}, // 304: 「演習」で他提督を圧倒せよ!\r
260             {302, new QuestPractice {Interval = Weekly, Max = 20, Win = true, Material = new[] {0, 0, 2, 1}}}, // 302: 大規模演習\r
261             {311, new QuestPractice {Interval = Daily, Max = 7, Win = true, Material = new[] {0, 2, 0, 0}}}, // 311: 精鋭艦隊演習\r
262             {318, new QuestSpec {Interval = Daily, Max = 3, Material = new[] {0, 2, 2, 0}, AdjustCount = false}}, // 318: 給糧艦「伊良湖」の支援\r
263 \r
264             {402, new QuestMission {Interval = Daily, Max = 3, Material = new[] {0, 0, 1, 0}}}, // 402: 「遠征」を3回成功させよう!\r
265             {403, new QuestMission {Interval = Daily, Max = 10, Material = new[] {0, 0, 0, 0}}}, // 403: 「遠征」を10回成功させよう!\r
266             {404, new QuestMission {Interval = Weekly, Max = 30, Material = new[] {0, 0, 3, 0}}}, // 404: 大規模遠征作戦、発令!\r
267             {410, new QuestMission {Interval = Weekly, Max = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 0, 0}}}, // 410: 南方への輸送作戦を成功させよ!\r
268             {411, new QuestMission {Interval = Weekly, Max = 6, Shift = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 2, 1}}}, // 411: 南方への鼠輸送を継続実施せよ!\r
269             {424, new QuestMission {Interval = Monthly, Max = 4, Shift = 1, Ids = new[] {5}, Material = new[] {0, 0, 0, 0}}}, // 424: 輸送船団護衛を強化せよ!\r
270             {426, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 4, 0}}}, // 426: 海上通商航路の警戒を厳とせよ!\r
271             {428, new QuestSpec {Interval = Quarterly, MaxArray = new[] {2, 2, 2}, Material = new[] {0, 0, 0, 3}}}, // 428: 近海に侵入する敵潜を制圧せよ!\r
272 \r
273             {503, new QuestSpec {Interval = Daily, Max = 5, Material = new[] {0, 2, 0, 0}}}, // 503: 艦隊大整備!\r
274             {504, new QuestSpec {Interval = Daily, Max = 15, Material = new[] {1, 0, 1, 0}}}, // 504: 艦隊酒保祭り!\r
275 \r
276             {605, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {1, 0, 1, 0}}}, // 605: 新装備「開発」指令\r
277             {606, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 1, 1, 0}}}, // 606: 新造艦「建造」指令\r
278             {607, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {0, 0, 2, 0}}}, // 607: 装備「開発」集中強化!\r
279             {608, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {1, 0, 2, 0}}}, // 608: 艦娘「建造」艦隊強化!\r
280             {609, new QuestSpec {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 609: 軍縮条約対応!\r
281             {619, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 0, 0, 1}}}, // 619: 装備の改修強化\r
282 \r
283             {613, new QuestSpec {Interval = Weekly, Max = 24, Material = new[] {0, 0, 0, 0}}}, // 613: 資源の再利用\r
284             {638, new QuestDestroyItem {Interval = Weekly, Max = 6, Items = new[] {21}, Material = new[] {0, 0, 2, 1}}}, // 638: 対空機銃量産\r
285             {663, new QuestDestroyItem {Interval = Quarterly, Max = 10, Items = new[] {3}, Material = new[] {0, 0, 3, 0}}}, // 663: 新型艤装の継続研究\r
286             {673, new QuestDestroyItem {Interval = Daily, Max = 4, Items = new[] {1}, Shift = 1, Material = new[] {0, 0, 1, 0}}}, // 673: 装備開発力の整備\r
287             {674, new QuestDestroyItem {Interval = Daily, Max = 3, Items = new[] {21}, Shift = 2, Material = new[] {0, 1, 1, 0}}}, // 674: 工廠環境の整備\r
288             {675, new QuestSpec {Interval = Quarterly, MaxArray = new[] {6, 4}, Material = new[] {0, 0, 0, 0}}}, // 675: 運用装備の統合整備\r
289             {676, new QuestSpec {Interval = Weekly, MaxArray = new[] {3, 3, 1}, Material = new[] {0, 1, 7, 0}}}, // 676: 装備開発力の集中整備\r
290             {677, new QuestSpec {Interval = Weekly, MaxArray = new[] {4, 2, 3}, Material = new[] {0, 5, 0, 0}}}, // 677: 継戦支援能力の整備\r
291             {678, new QuestSpec {Interval = Quarterly, MaxArray = new[] {3, 5}, Material = new[] {0, 0, 8, 0}}}, // 678: 主力艦上戦闘機の更新\r
292 \r
293             {702, new QuestPowerup {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 702: 艦の「近代化改修」を実施せよ!\r
294             {703, new QuestPowerup {Interval = Weekly, Max = 15, Material = new[] {1, 0, 2, 0}}} // 703: 「近代化改修」を進め、戦備を整えよ!\r
295         };\r
296         // @formatter:on\r
297 \r
298         private readonly Dictionary<int, QuestCount> _countDict = new Dictionary<int, QuestCount>();\r
299 \r
300         public QuestCount GetCount(int id)\r
301         {\r
302             if (_countDict.TryGetValue(id, out var value))\r
303                 return value;\r
304             if (QuestSpecs.TryGetValue(id, out var spec))\r
305             {\r
306                 var nowArray = spec.MaxArray?.Select(x => 0).ToArray();\r
307                 return _countDict[id] = new QuestCount\r
308                 {\r
309                     Id = id,\r
310                     Now = 0,\r
311                     NowArray = nowArray,\r
312                     Spec = spec\r
313                 };\r
314             }\r
315             return new QuestCount {Spec = new QuestSpec {Material = new int[0], AdjustCount = false}};\r
316         }\r
317 \r
318         public void Remove(int id)\r
319         {\r
320             _countDict.Remove(id);\r
321         }\r
322 \r
323         public void Remove(QuestInterval interval)\r
324         {\r
325             foreach (var id in\r
326                 _countDict.Where(pair => pair.Value.Spec.Interval == interval).Select(pair => pair.Key).ToArray())\r
327             {\r
328                 _countDict.Remove(id);\r
329             }\r
330         }\r
331 \r
332         public IEnumerable<QuestCount> CountList\r
333         {\r
334             get => _countDict.Values.Where(c => c.Now > 0 || (c.NowArray?.Any(n => n > 0) ?? false));\r
335             set\r
336             {\r
337                 if (value == null)\r
338                     return;\r
339                 foreach (var count in value)\r
340                 {\r
341                     count.Spec = QuestSpecs[count.Id];\r
342                     _countDict[count.Id] = count;\r
343                 }\r
344             }\r
345         }\r
346     }\r
347 \r
348     public class QuestInfo : IHaveState\r
349     {\r
350         private readonly SortedDictionary<int, QuestStatus> _quests = new SortedDictionary<int, QuestStatus>();\r
351         private readonly QuestCountList _countList = new QuestCountList();\r
352         private readonly ItemInfo _itemInfo;\r
353         private readonly BattleInfo _battleInfo;\r
354         private readonly Func<DateTime> _nowFunc = () => DateTime.Now;\r
355         private DateTime _lastReset;\r
356 \r
357         private readonly Color[] _color =\r
358         {\r
359             Color.FromArgb(60, 141, 76), Color.FromArgb(232, 57, 41), Color.FromArgb(136, 204, 120),\r
360             Color.FromArgb(52, 147, 185), Color.FromArgb(220, 198, 126), Color.FromArgb(168, 111, 76),\r
361             Color.FromArgb(200, 148, 231), Color.FromArgb(232, 57, 41)\r
362         };\r
363 \r
364         public int AcceptMax { get; set; } = 5;\r
365 \r
366         public QuestStatus[] Quests => _quests.Values.ToArray();\r
367 \r
368         public QuestInfo(ItemInfo itemInfo, BattleInfo battleInfo, Func<DateTime> nowFunc = null)\r
369         {\r
370             _itemInfo = itemInfo;\r
371             _battleInfo = battleInfo;\r
372             if (nowFunc != null)\r
373                 _nowFunc = nowFunc;\r
374         }\r
375 \r
376         public void InspectQuestList(dynamic json)\r
377         {\r
378             ResetQuests();\r
379             if (json.api_list == null)\r
380                 return;\r
381             for (var i = 0; i < 2; i++)\r
382             {\r
383                 foreach (var entry in json.api_list)\r
384                 {\r
385                     if (entry is double) // -1の場合がある。\r
386                         continue;\r
387 \r
388                     var id = (int)entry.api_no;\r
389                     var state = (int)entry.api_state;\r
390                     var progress = (int)entry.api_progress_flag;\r
391                     var cat = (int)entry.api_category;\r
392                     var name = (string)entry.api_title;\r
393                     var detail = ((string)entry.api_detail).Replace("<br>", "\r\n");\r
394                     var material = (int[])entry.api_get_material;\r
395 \r
396                     switch (progress)\r
397                     {\r
398                         case 0:\r
399                             break;\r
400                         case 1:\r
401                             progress = 50;\r
402                             break;\r
403                         case 2:\r
404                             progress = 80;\r
405                             break;\r
406                     }\r
407                     switch (state)\r
408                     {\r
409                         case 1:\r
410                             if (_quests.Remove(id))\r
411                                 NeedSave = true;\r
412                             break;\r
413                         case 3:\r
414                             progress = 100;\r
415                             goto case 2;\r
416                         case 2:\r
417                             AddQuest(id, cat, name, detail, material, progress, true);\r
418                             break;\r
419                     }\r
420                 }\r
421                 if (_quests.Count <= AcceptMax)\r
422                     break;\r
423                 /*\r
424                  * ほかのPCで任務を達成した場合、任務が消えずに受領した任務の数が_questCountを超えることがある。\r
425                  * その場合はいったん任務をクリアして、現在のページの任務だけを登録し直す。\r
426                  */\r
427                 _quests.Clear();\r
428             }\r
429         }\r
430 \r
431         private void AddQuest(int id, int category, string name, string detail, int[] material, int progress,\r
432             bool adjustCount)\r
433         {\r
434             var count = _countList.GetCount(id);\r
435             if (adjustCount)\r
436             {\r
437                 if (count.AdjustCount(progress))\r
438                     NeedSave = true;\r
439             }\r
440             _quests[id] = new QuestStatus\r
441             {\r
442                 Id = id,\r
443                 Category = category,\r
444                 Name = name,\r
445                 Detail = detail,\r
446                 Material = adjustCount ? material?.Concat(count.Spec.Material).ToArray() : material,\r
447                 Count = count,\r
448                 Progress = progress,\r
449                 Color = category <= _color.Length ? _color[category - 1] : Control.DefaultBackColor\r
450             };\r
451         }\r
452 \r
453         public void ClearQuests()\r
454         {\r
455             _quests.Clear();\r
456         }\r
457 \r
458         private void ResetQuests()\r
459         {\r
460             var now = _nowFunc();\r
461             var daily = now.Date.AddHours(5);\r
462             if (!(_lastReset < daily && daily <= now))\r
463                 return;\r
464             _quests.Clear(); // 前日に未消化のデイリーを消す。\r
465             _countList.Remove(QuestInterval.Daily);\r
466             var weekly = now.Date.AddDays(-((6 + (int)now.DayOfWeek) % 7)).AddHours(5);\r
467             if (_lastReset < weekly && weekly <= now)\r
468                 _countList.Remove(QuestInterval.Weekly);\r
469             var monthly = new DateTime(now.Year, now.Month, 1, 5, 0, 0);\r
470             if (_lastReset < monthly && monthly <= now)\r
471                 _countList.Remove(QuestInterval.Monthly);\r
472             var season = now.Month / 3;\r
473             var quarterly = new DateTime(now.Year - (season == 0 ? 1 : 0), (season == 0 ? 12 : season * 3), 1, 5, 0, 0);\r
474             if (_lastReset < quarterly && quarterly <= now)\r
475                 _countList.Remove(QuestInterval.Quarterly);\r
476             _lastReset = now;\r
477             NeedSave = true;\r
478         }\r
479 \r
480 \r
481         private int _map;\r
482         private bool _boss;\r
483 \r
484         public void InspectMapStart(dynamic json)\r
485         {\r
486             if (_quests.TryGetValue(214, out var ago)) // あ号\r
487                 ago.Count.NowArray[0]++;\r
488             InspectMapNext(json);\r
489         }\r
490 \r
491         public void InspectMapNext(dynamic json)\r
492         {\r
493             _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;\r
494             _boss = (int)json.api_event_id == 5;\r
495 \r
496             if (_quests.TryGetValue(861, out var q861))\r
497             {\r
498                 if (_map == 16 && (int)json.api_event_id == 8)\r
499                 {\r
500                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
501                         .ToArray();\r
502                     if (fleet.Count(s => s == 10 || s == 22) == 2)\r
503                         IncrementCount(q861.Count);\r
504                 }\r
505             }\r
506         }\r
507 \r
508         public void InspectBattleResult(dynamic json)\r
509         {\r
510             var rank = json.api_win_rank;\r
511             foreach (var quest in _quests.Values)\r
512             {\r
513                 var count = quest.Count;\r
514                 switch (count.Spec)\r
515                 {\r
516                     case QuestSortie sortie:\r
517                         if (count.Id == 216 && !_boss || sortie.Check(rank, _map, _boss))\r
518                             IncrementCount(count);\r
519                         break;\r
520                     case QuestEnemyType enemyType:\r
521                         var num = enemyType.CountResult(\r
522                             _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));\r
523                         if (num > 0)\r
524                             AddCount(count, num);\r
525                         break;\r
526                 }\r
527             }\r
528             if (_quests.TryGetValue(214, out var ago))\r
529             {\r
530                 var array = ago.Count.NowArray;\r
531                 if (_boss)\r
532                 {\r
533                     array[2]++;\r
534                     if (QuestSortie.CompareRank(rank, "B") <= 0)\r
535                         array[3]++;\r
536                     NeedSave = true;\r
537                 }\r
538                 if (rank == "S")\r
539                 {\r
540                     array[1]++;\r
541                     NeedSave = true;\r
542                 }\r
543             }\r
544             if (_quests.TryGetValue(249, out var q249))\r
545             {\r
546                 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
547                 {\r
548                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id)\r
549                         .ToArray();\r
550                     if (fleet.Intersect(new[] {62, 63, 64, 265, 266, 268, 319, 192, 194}).Count() == 3)\r
551                         IncrementCount(q249.Count);\r
552                 }\r
553             }\r
554             if (_quests.TryGetValue(257, out var q257))\r
555             {\r
556                 if (_map == 14 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
557                 {\r
558                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
559                         .ToArray();\r
560                     if (fleet[0] == 3 && fleet.Count(s => s == 3) <= 3 && fleet.All(s => s == 2 || s == 3))\r
561                         IncrementCount(q257.Count);\r
562                 }\r
563             }\r
564             if (_quests.TryGetValue(259, out var q259))\r
565             {\r
566                 if (_map == 51 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
567                 {\r
568                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec).ToArray();\r
569                     var senkan = new[]\r
570                     {\r
571                         131, 136, // 大和\r
572                         143, 148, 546, // 武蔵\r
573                         80, 275, 541, // 長門\r
574                         81, 276, // 陸奥\r
575                         26, 286, 411, // 扶桑\r
576                         27, 287, 412, // 山城\r
577                         77, 82, // 伊勢\r
578                         87, 88 // 日向\r
579                     };\r
580                     if (fleet.Select(s => s.Id).Intersect(senkan).Count() == 3 &&\r
581                         fleet.Count(s => s.ShipType == 3) > 0)\r
582                     {\r
583                         IncrementCount(q259.Count);\r
584                     }\r
585                 }\r
586             }\r
587             if (_quests.TryGetValue(266, out var q266))\r
588             {\r
589                 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
590                 {\r
591                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
592                         .ToArray();\r
593                     if (fleet[0] == 2 && fleet.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5}))\r
594                         IncrementCount(q266.Count);\r
595                 }\r
596             }\r
597             if (_quests.TryGetValue(854, out var opz) && _boss)\r
598             {\r
599                 var array = opz.Count.NowArray;\r
600                 switch (_map)\r
601                 {\r
602                     case 24 when QuestSortie.CompareRank(rank, "A") <= 0:\r
603                         array[0]++;\r
604                         NeedSave = true;\r
605                         break;\r
606                     case 61 when QuestSortie.CompareRank(rank, "A") <= 0:\r
607                         array[1]++;\r
608                         NeedSave = true;\r
609                         break;\r
610                     case 63 when QuestSortie.CompareRank(rank, "A") <= 0:\r
611                         array[2]++;\r
612                         NeedSave = true;\r
613                         break;\r
614                     case 64 when QuestSortie.CompareRank(rank, "S") <= 0:\r
615                         array[3]++;\r
616                         NeedSave = true;\r
617                         break;\r
618                 }\r
619             }\r
620             if (_quests.TryGetValue(862, out var q862))\r
621             {\r
622                 if (_map == 63 && _boss && QuestSortie.CompareRank(rank, "A") <= 0)\r
623                 {\r
624                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType).ToArray();\r
625                     if (fleet.Count(s => s == 3) == 2 && fleet.Count(s => s == 16) == 1)\r
626                         IncrementCount(q862.Count);\r
627                 }\r
628             }\r
629             if (_quests.TryGetValue(873, out var q873))\r
630             {\r
631                 if (_battleInfo.Result.Friend.Main.Count(s => s.NowHp > 0 && s.Spec.ShipType == 3) >= 1 &&\r
632                     _boss && QuestSortie.CompareRank(rank, "A") <= 0)\r
633                 {\r
634                     var array = q873.Count.NowArray;\r
635                     switch (_map)\r
636                     {\r
637                         case 31:\r
638                             array[0]++;\r
639                             NeedSave = true;\r
640                             break;\r
641                         case 32:\r
642                             array[1]++;\r
643                             NeedSave = true;\r
644                             break;\r
645                         case 33:\r
646                             array[2]++;\r
647                             NeedSave = true;\r
648                             break;\r
649                     }\r
650                 }\r
651             }\r
652             if (_quests.TryGetValue(875, out var q875))\r
653             {\r
654                 if (_map == 54 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
655                 {\r
656                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id).ToArray();\r
657                     if (fleet.Contains(543) && fleet.Intersect(new []{344, 345, 359}).Any())\r
658                         IncrementCount(q875.Count);\r
659                 }\r
660             }\r
661         }\r
662 \r
663         public void InspectPracticeResult(dynamic json)\r
664         {\r
665             foreach (var quest in _quests.Values)\r
666             {\r
667                 var count = quest.Count;\r
668                 if (!(count.Spec is QuestPractice practice))\r
669                     continue;\r
670                 if (practice.Check(json.api_win_rank))\r
671                     IncrementCount(count);\r
672             }\r
673             if (_quests.TryGetValue(318, out var q318))\r
674             {\r
675                 if (QuestSortie.CompareRank(json.api_win_rank, "B") <= 0 &&\r
676                     _battleInfo.Result.Friend.Main.Count(s => s.Spec.ShipType == 3) >= 2)\r
677                 {\r
678                     IncrementCount(q318.Count);\r
679                 }\r
680             }\r
681         }\r
682 \r
683         private readonly int[] _missionId = new int[ShipInfo.FleetCount];\r
684 \r
685         public void InspectDeck(dynamic json)\r
686         {\r
687             foreach (var entry in json)\r
688                 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];\r
689         }\r
690 \r
691         public void InspectMissionResult(string request, dynamic json)\r
692         {\r
693             var values = HttpUtility.ParseQueryString(request);\r
694             var deck = int.Parse(values["api_deck_id"]);\r
695             if ((int)json.api_clear_result == 0)\r
696                 return;\r
697             var mid = _missionId[deck - 1];\r
698             foreach (var quest in _quests.Values)\r
699             {\r
700                 var count = quest.Count;\r
701                 if (!(count.Spec is QuestMission mission))\r
702                     continue;\r
703                 if (mission.Check(mid))\r
704                     IncrementCount(count);\r
705             }\r
706             if (_quests.TryGetValue(426, out var q426))\r
707             {\r
708                 var count = q426.Count;\r
709                 switch (mid)\r
710                 {\r
711                     case 3:\r
712                         count.NowArray[0]++;\r
713                         break;\r
714                     case 4:\r
715                         count.NowArray[1]++;\r
716                         break;\r
717                     case 5:\r
718                         count.NowArray[2]++;\r
719                         break;\r
720                     case 10:\r
721                         count.NowArray[3]++;\r
722                         break;\r
723                 }\r
724             }\r
725             if (_quests.TryGetValue(428, out var q428))\r
726             {\r
727                 var count = q428.Count;\r
728                 switch (mid)\r
729                 {\r
730                     case 4:\r
731                         count.NowArray[0]++;\r
732                         break;\r
733                     case 101:\r
734                         count.NowArray[1]++;\r
735                         break;\r
736                     case 102:\r
737                         count.NowArray[2]++;\r
738                         break;\r
739                 }\r
740             }\r
741         }\r
742 \r
743         private void IncrementCount(QuestCount count)\r
744         {\r
745             count.Now++;\r
746             NeedSave = true;\r
747         }\r
748 \r
749         private void AddCount(QuestCount count, int value)\r
750         {\r
751             count.Now += value;\r
752             NeedSave = true;\r
753         }\r
754 \r
755         private void IncrementCount(int id)\r
756         {\r
757             AddCount(id, 1);\r
758         }\r
759 \r
760         private void AddCount(int id, int value)\r
761         {\r
762             if (_quests.TryGetValue(id, out var quest))\r
763             {\r
764                 quest.Count.Now += value;\r
765                 NeedSave = true;\r
766             }\r
767         }\r
768 \r
769         public void CountNyukyo() => IncrementCount(503);\r
770 \r
771         public void CountCharge() => IncrementCount(504);\r
772 \r
773         public void CountCreateItem()\r
774         {\r
775             IncrementCount(605);\r
776             IncrementCount(607);\r
777         }\r
778 \r
779         public void CountCreateShip()\r
780         {\r
781             IncrementCount(606);\r
782             IncrementCount(608);\r
783         }\r
784 \r
785         public void InspectDestroyShip(string request)\r
786         {\r
787             AddCount(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);\r
788         }\r
789 \r
790         public void CountRemodelSlot() => IncrementCount(619);\r
791 \r
792         public void InspectDestroyItem(string request, dynamic json)\r
793         {\r
794             var values = HttpUtility.ParseQueryString(request);\r
795             var items = values["api_slotitem_ids"].Split(',')\r
796                 .Select(id => _itemInfo.GetStatus(int.Parse(id)).Spec).ToArray();\r
797             IncrementCount(613); // 613: 資源の再利用\r
798             foreach (var quest in _quests.Values)\r
799             {\r
800                 var count = quest.Count;\r
801                 if (!(count.Spec is QuestDestroyItem destroy))\r
802                     continue;\r
803                 AddCount(count, items.Count(spec => destroy.Check(spec.Type)));\r
804             }\r
805             if (_quests.TryGetValue(675, out var q675))\r
806             {\r
807                 q675.Count.NowArray[0] += items.Count(spec => spec.Type == 6);\r
808                 q675.Count.NowArray[1] += items.Count(spec => spec.Type == 21);\r
809                 NeedSave = true;\r
810             }\r
811             if (_quests.TryGetValue(676, out var q676))\r
812             {\r
813                 q676.Count.NowArray[0] += items.Count(spec => spec.Type == 2);\r
814                 q676.Count.NowArray[1] += items.Count(spec => spec.Type == 4);\r
815                 q676.Count.NowArray[2] += items.Count(spec => spec.Type == 30);\r
816                 NeedSave = true;\r
817             }\r
818             if (_quests.TryGetValue(677, out var q677))\r
819             {\r
820                 q677.Count.NowArray[0] += items.Count(spec => spec.Type == 3);\r
821                 q677.Count.NowArray[1] += items.Count(spec => spec.Type == 10);\r
822                 q677.Count.NowArray[2] += items.Count(spec => spec.Type == 5);\r
823                 NeedSave = true;\r
824             }\r
825             if (_quests.TryGetValue(678, out var q678))\r
826             {\r
827                 q678.Count.NowArray[0] += items.Count(spec => spec.Id == 19);\r
828                 q678.Count.NowArray[1] += items.Count(spec => spec.Id == 20);\r
829                 NeedSave = true;\r
830             }\r
831         }\r
832 \r
833         public void InspectPowerup(dynamic json)\r
834         {\r
835             if ((int)json.api_powerup_flag == 0)\r
836                 return;\r
837             foreach (var quest in _quests.Values)\r
838             {\r
839                 var count = quest.Count;\r
840                 if (!(count.Spec is QuestPowerup))\r
841                     continue;\r
842                 IncrementCount(count);\r
843             }\r
844         }\r
845 \r
846         public void InspectStop(string request)\r
847         {\r
848             var values = HttpUtility.ParseQueryString(request);\r
849             _quests.Remove(int.Parse(values["api_quest_id"]));\r
850             NeedSave = true;\r
851         }\r
852 \r
853         public void InspectClearItemGet(string request)\r
854         {\r
855             var values = HttpUtility.ParseQueryString(request);\r
856             var id = int.Parse(values["api_quest_id"]);\r
857             _countList.Remove(id);\r
858             _quests.Remove(id);\r
859             NeedSave = true;\r
860         }\r
861 \r
862         public bool NeedSave { get; private set; }\r
863 \r
864         public void SaveState(Status status)\r
865         {\r
866             NeedSave = false;\r
867             status.QuestLastReset = _lastReset;\r
868             if (_quests != null)\r
869                 status.QuestList = _quests.Values.ToArray();\r
870             if (_countList != null)\r
871                 status.QuestCountList = _countList.CountList.ToArray();\r
872         }\r
873 \r
874         public void LoadState(Status status)\r
875         {\r
876             _lastReset = status.QuestLastReset;\r
877             if (status.QuestCountList != null)\r
878                 _countList.CountList = status.QuestCountList;\r
879             if (status.QuestList != null)\r
880             {\r
881                 _quests.Clear();\r
882                 foreach (var q in status.QuestList)\r
883                     AddQuest(q.Id, q.Category, q.Name, q.Detail, q.Material, q.Progress, false);\r
884             }\r
885         }\r
886     }\r
887 }