OSDN Git Service

陸奥改二の一斉射に対応する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / 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 KancolleSniffer.Util;\r
22 using static System.Math;\r
23 \r
24 namespace KancolleSniffer.Model\r
25 {\r
26     public class QuestStatus\r
27     {\r
28         public int Id { get; set; }\r
29         public int Category { get; set; }\r
30         public QuestInterval Interval { get; set; }\r
31         public string Name { get; set; }\r
32         public string Detail { get; set; }\r
33         public int[] Material { get; set; }\r
34         public int Progress { get; set; }\r
35 \r
36         [XmlIgnore]\r
37         public QuestCount Count { get; set; }\r
38 \r
39         [XmlIgnore]\r
40         public Color Color { get; set; }\r
41 \r
42         public string ToToolTip() =>\r
43             Detail +\r
44             (Material == null || Material.All(x => x == 0)\r
45                 ? ""\r
46                 : "\r\n" + string.Join(" ",\r
47                       new[] {"燃", "弾", "鋼", "ボ", "建造", "修復", "開発", "改修"}\r
48                           .Zip(Material, (m, num) => num == 0 ? "" : m + num)\r
49                           .Where(s => !string.IsNullOrEmpty(s))));\r
50     }\r
51 \r
52     public enum QuestInterval\r
53     {\r
54         // ReSharper disable once UnusedMember.Global\r
55         Other,\r
56         Daily,\r
57         Weekly,\r
58         Monthly,\r
59         Quarterly\r
60     }\r
61 \r
62     public class QuestSpec\r
63     {\r
64         public QuestInterval Interval { get; set; }\r
65         public int Max { get; set; }\r
66         public int[] MaxArray { get; set; }\r
67         public bool AdjustCount { get; set; } = true;\r
68         public int Shift { get; set; }\r
69         public int[] Material { get; set; }\r
70     }\r
71 \r
72     public class QuestSortie : QuestSpec\r
73     {\r
74         public string Rank { get; set; }\r
75         public int[] Maps { get; set; }\r
76 \r
77         public static int CompareRank(string a, string b)\r
78         {\r
79             const string ranks = "SABCDE";\r
80             return ranks.IndexOf(a, StringComparison.Ordinal) -\r
81                    ranks.IndexOf(b, StringComparison.Ordinal);\r
82         }\r
83 \r
84         public bool Check(string rank, int map, bool boss)\r
85         {\r
86             return (Rank == null || CompareRank(rank, Rank) <= 0) &&\r
87                    (Maps == null || Maps.Contains(map) && boss);\r
88         }\r
89     }\r
90 \r
91     public class QuestEnemyType : QuestSpec\r
92     {\r
93         public int[] EnemyType { get; set; } = new int[0];\r
94 \r
95         public int CountResult(IEnumerable<ShipStatus> enemyResult) =>\r
96             enemyResult.Count(ship => ship.NowHp == 0 && EnemyType.Contains(ship.Spec.ShipType));\r
97     }\r
98 \r
99     public class QuestPractice : QuestSpec\r
100     {\r
101         public bool Win { get; set; }\r
102         public bool Check(string rank) => !Win || QuestSortie.CompareRank(rank, "B") <= 0;\r
103     }\r
104 \r
105     public class QuestMission : QuestSpec\r
106     {\r
107         public int[] Ids { get; set; }\r
108         public bool Check(int id) => Ids == null || Ids.Contains(id);\r
109     }\r
110 \r
111     public class QuestDestroyItem : QuestSpec\r
112     {\r
113         public int[] Types { get; set; }\r
114         public int[] Ids { get; set; }\r
115 \r
116         public bool Count(QuestCount count, ItemSpec[] specs)\r
117         {\r
118             if (count.NowArray == null)\r
119             {\r
120                 var num = specs.Count(spec => Types?.Contains(spec.Type) ?? (Ids?.Contains(spec.Id) ?? true));\r
121                 count.Now += num;\r
122                 return num > 0;\r
123             }\r
124             if (Types == null && Ids == null)\r
125                 return false;\r
126             var result = false;\r
127             for (var i = 0; i < count.NowArray.Length; i++)\r
128             {\r
129                 var num = specs.Count(spec => Types != null ? Types[i] == spec.Type : Ids[i] == spec.Id);\r
130                 count.NowArray[i] += num;\r
131                 if (num > 0)\r
132                     result = true;\r
133             }\r
134             return result;\r
135         }\r
136     }\r
137 \r
138     public class QuestPowerUp : QuestSpec\r
139     {\r
140     }\r
141 \r
142     public class QuestCount\r
143     {\r
144         public int Id { get; set; }\r
145         public int Now { get; set; }\r
146         public int[] NowArray { get; set; }\r
147 \r
148         [XmlIgnore]\r
149         public QuestSpec Spec { get; set; }\r
150 \r
151         public bool AdjustCount(int progress)\r
152         {\r
153             if (!Spec.AdjustCount)\r
154                 return false;\r
155             if (NowArray != null)\r
156             {\r
157                 if (progress != 100)\r
158                     return false;\r
159                 NowArray = NowArray.Zip(Spec.MaxArray, Max).ToArray();\r
160                 return true;\r
161             }\r
162             var next = 0;\r
163             switch (progress)\r
164             {\r
165                 case 0:\r
166                     next = 50;\r
167                     break;\r
168                 case 50:\r
169                     next = 80;\r
170                     break;\r
171                 case 80:\r
172                     next = 100;\r
173                     break;\r
174                 case 100:\r
175                     next = 100000;\r
176                     break;\r
177             }\r
178             var now = Now + Spec.Shift;\r
179             var max = Spec.Max + Spec.Shift;\r
180             var low = (int)Ceiling(max * progress / 100.0);\r
181             if (low >= max && progress != 100)\r
182                 low = max - 1;\r
183             var high = (int)Ceiling(max * next / 100.0);\r
184             if (now < low)\r
185             {\r
186                 Now = low - Spec.Shift;\r
187                 return true;\r
188             }\r
189             if (now >= high)\r
190             {\r
191                 Now = high - 1 - Spec.Shift;\r
192                 return true;\r
193             }\r
194             return false;\r
195         }\r
196 \r
197         public override string ToString()\r
198         {\r
199             if (Id == 280 || Id == 426 || Id == 854 || Id == 873 || Id == 888 || Id == 894)\r
200                 return $"{NowArray.Count(n => n >= 1)}/{Spec.MaxArray.Length}";\r
201             return NowArray != null\r
202                 ? string.Join(" ", NowArray.Zip(Spec.MaxArray, (n, m) => $"{n}/{m}"))\r
203                 : $"{Now}/{Spec.Max}";\r
204         }\r
205 \r
206         public string ToToolTip()\r
207         {\r
208             switch (Id)\r
209             {\r
210                 case 280:\r
211                     return string.Join(" ",\r
212                         new[] {"1-2", "1-3", "1-4", "2-1"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
213                             .Where(s => !string.IsNullOrEmpty(s)));\r
214                 case 426:\r
215                     return string.Join(" ",\r
216                         new[] {"警備任務", "対潜警戒任務", "海上護衛任務", "強硬偵察任務"}\r
217                             .Zip(NowArray, (mission, n) => n >= 1 ? mission : "")\r
218                             .Where(s => !string.IsNullOrEmpty(s)));\r
219                 case 428:\r
220                     return string.Join(" ",\r
221                         new[] {"対潜警戒任務", "海峡警備行動", "長時間対潜警戒"}.Zip(NowArray, (mission, n) => n >= 1 ? mission + n : "")\r
222                             .Where(s => !string.IsNullOrEmpty(s)));\r
223                 case 854:\r
224                     return string.Join(" ",\r
225                         new[] {"2-4", "6-1", "6-3", "6-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
226                             .Where(s => !string.IsNullOrEmpty(s)));\r
227                 case 873:\r
228                     return string.Join(" ",\r
229                         new[] {"3-1", "3-2", "3-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
230                             .Where(s => !string.IsNullOrEmpty(s)));\r
231                 case 888:\r
232                     return string.Join(" ",\r
233                         new[] {"5-1", "5-3", "5-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
234                             .Where(s => !string.IsNullOrEmpty(s)));\r
235                 case 688:\r
236                     return string.Join(" ",\r
237                         new[] {"艦戦", "艦爆", "艦攻", "水偵"}.Zip(NowArray, (type, n) => n >= 1 ? type + n : "")\r
238                             .Where(s => !string.IsNullOrEmpty(s)));\r
239                 case 893:\r
240                     return string.Join(" ",\r
241                         new[] {"1-5", "7-1", "7-2G", "7-2M"}.Zip(NowArray, (map, n) => n >= 1 ? $"{map}:{n}" : "")\r
242                             .Where(s => !string.IsNullOrEmpty(s)));\r
243                 case 894:\r
244                     return string.Join(" ",\r
245                         new[] {"1-3", "1-4", "2-1", "2-2", "2-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
246                             .Where(s => !string.IsNullOrEmpty(s)));\r
247             }\r
248             return "";\r
249         }\r
250 \r
251         public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ??\r
252                                Spec.Max != 0 && Now >= Spec.Max;\r
253     }\r
254 \r
255     public class QuestInfo : IHaveState\r
256     {\r
257         private readonly SortedDictionary<int, QuestStatus> _quests = new SortedDictionary<int, QuestStatus>();\r
258         private readonly QuestCountList _countList = new QuestCountList();\r
259         private readonly ItemInfo _itemInfo;\r
260         private readonly BattleInfo _battleInfo;\r
261         private readonly Func<DateTime> _nowFunc = () => DateTime.Now;\r
262         private DateTime _lastReset;\r
263         private IEnumerable<QuestStatus> _clearedQuest = new List<QuestStatus>();\r
264 \r
265         private readonly Color[] _color =\r
266         {\r
267             Color.FromArgb(60, 141, 76), Color.FromArgb(232, 57, 41), Color.FromArgb(136, 204, 120),\r
268             Color.FromArgb(52, 147, 185), Color.FromArgb(220, 198, 126), Color.FromArgb(168, 111, 76),\r
269             Color.FromArgb(200, 148, 231), Color.FromArgb(232, 57, 41)\r
270         };\r
271 \r
272         public int AcceptMax { get; set; } = 5;\r
273 \r
274         public QuestStatus[] Quests => _quests.Values.ToArray();\r
275 \r
276         public QuestInfo(ItemInfo itemInfo, BattleInfo battleInfo, Func<DateTime> nowFunc = null)\r
277         {\r
278             _itemInfo = itemInfo;\r
279             _battleInfo = battleInfo;\r
280             if (nowFunc != null)\r
281                 _nowFunc = nowFunc;\r
282         }\r
283 \r
284         public void GetNotifications(out string[] notify, out string[] stop)\r
285         {\r
286             var cleared = _quests.Values.Where(q => q.Count.Cleared).ToArray();\r
287             notify = cleared.Except(_clearedQuest, new QuestComparer()).Select(q => q.Name).ToArray();\r
288             stop = _clearedQuest.Except(cleared, new QuestComparer()).Select(q => q.Name).ToArray();\r
289             _clearedQuest = cleared;\r
290         }\r
291 \r
292         private class QuestComparer : IEqualityComparer<QuestStatus>\r
293         {\r
294             public bool Equals(QuestStatus x, QuestStatus y)\r
295             {\r
296                 return x?.Id == y?.Id;\r
297             }\r
298 \r
299             public int GetHashCode(QuestStatus obj)\r
300             {\r
301                 return obj.Id;\r
302             }\r
303         }\r
304 \r
305         private readonly QuestInterval[] _intervals =\r
306         {\r
307             QuestInterval.Daily, QuestInterval.Weekly, QuestInterval.Monthly,\r
308             QuestInterval.Other, QuestInterval.Quarterly\r
309         };\r
310 \r
311         private readonly int[] _progress = new[] {0, 50, 80};\r
312 \r
313         public void InspectQuestList(dynamic json)\r
314         {\r
315             ResetQuests();\r
316             if (json.api_list == null)\r
317                 return;\r
318             for (var i = 0; i < 2; i++)\r
319             {\r
320                 foreach (var entry in json.api_list)\r
321                 {\r
322                     if (entry is double) // -1の場合がある。\r
323                         continue;\r
324                     var quest = new QuestStatus\r
325                     {\r
326                         Id = (int)entry.api_no,\r
327                         Category = (int)entry.api_category,\r
328                         Progress = _progress[(int)entry.api_progress_flag],\r
329                         Interval = _intervals[(int)entry.api_type - 1],\r
330                         Name = (string)entry.api_title,\r
331                         Detail = ((string)entry.api_detail).Replace("<br>", "\r\n"),\r
332                         Material = (int[])entry.api_get_material\r
333                     };\r
334                     var state = (int)entry.api_state;\r
335                     switch (state)\r
336                     {\r
337                         case 1:\r
338                             if (_quests.Remove(quest.Id))\r
339                                 NeedSave = true;\r
340                             break;\r
341                         case 3:\r
342                             quest.Progress = 100;\r
343                             goto case 2;\r
344                         case 2:\r
345                             AddQuest(quest, true);\r
346                             break;\r
347                     }\r
348                 }\r
349                 if (_quests.Count <= AcceptMax)\r
350                     break;\r
351                 /*\r
352                  * ほかのPCで任務を達成した場合、任務が消えずに受領した任務の数が_questCountを超えることがある。\r
353                  * その場合はいったん任務をクリアして、現在のページの任務だけを登録し直す。\r
354                  */\r
355                 _quests.Clear();\r
356             }\r
357         }\r
358 \r
359         private void AddQuest(QuestStatus quest, bool adjustCount)\r
360         {\r
361             var count = _countList.GetCount(quest.Id);\r
362             if (adjustCount)\r
363             {\r
364                 if (count.AdjustCount(quest.Progress))\r
365                     NeedSave = true;\r
366                 quest.Material = quest.Material.Concat(count.Spec.Material).ToArray();\r
367             }\r
368             quest.Count = count;\r
369             quest.Color = quest.Category <= _color.Length ? _color[quest.Category - 1] : Control.DefaultBackColor;\r
370             _quests[quest.Id] = quest;\r
371         }\r
372 \r
373         public void ClearQuests()\r
374         {\r
375             _quests.Clear();\r
376         }\r
377 \r
378         private void ResetQuests()\r
379         {\r
380             var now = _nowFunc();\r
381             var daily = now.Date.AddHours(5);\r
382             if (now.Hour < 5)\r
383                 daily = daily.AddDays(-1);\r
384             if (!(_lastReset < daily && daily <= now))\r
385                 return;\r
386             RemoveQuest(QuestInterval.Daily);\r
387             _countList.Remove(QuestInterval.Daily);\r
388             var weekly = now.Date.AddDays(-((6 + (int)now.DayOfWeek) % 7)).AddHours(5);\r
389             if (_lastReset < weekly && weekly <= now)\r
390             {\r
391                 RemoveQuest(QuestInterval.Weekly);\r
392                 _countList.Remove(QuestInterval.Weekly);\r
393             }\r
394             var monthly = new DateTime(now.Year, now.Month, 1, 5, 0, 0);\r
395             if (_lastReset < monthly && monthly <= now)\r
396             {\r
397                 RemoveQuest(QuestInterval.Monthly);\r
398                 _countList.Remove(QuestInterval.Monthly);\r
399             }\r
400             var season = now.Month / 3;\r
401             var quarterly = new DateTime(now.Year - (season == 0 ? 1 : 0), (season == 0 ? 12 : season * 3), 1, 5, 0, 0);\r
402             if (_lastReset < quarterly && quarterly <= now)\r
403             {\r
404                 RemoveQuest(QuestInterval.Quarterly);\r
405                 _countList.Remove(QuestInterval.Quarterly);\r
406             }\r
407             _lastReset = now;\r
408             NeedSave = true;\r
409         }\r
410 \r
411         private void RemoveQuest(QuestInterval interval)\r
412         {\r
413             foreach (var id in\r
414                 (from kv in _quests\r
415                     where kv.Value.Count.Spec.Interval == interval || // 輸送5と空母3はカウンタを見ないとデイリーにならない\r
416                           kv.Value.Interval == interval\r
417                     select kv.Key).ToArray())\r
418                 _quests.Remove(id);\r
419         }\r
420 \r
421         private int _map;\r
422         private int _cell;\r
423         private bool _boss;\r
424 \r
425         public void InspectMapStart(dynamic json)\r
426         {\r
427             if (_quests.TryGetValue(214, out var ago)) // あ号\r
428                 ago.Count.NowArray[0]++;\r
429             InspectMapNext(json);\r
430         }\r
431 \r
432         public void InspectMapNext(dynamic json)\r
433         {\r
434             _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;\r
435             _cell = json.api_no() ? (int)json.api_no : 0;\r
436             _boss = (int)json.api_event_id == 5;\r
437 \r
438             if (_quests.TryGetValue(861, out var q861))\r
439             {\r
440                 if (_map == 16 && (int)json.api_event_id == 8)\r
441                 {\r
442                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
443                         .ToArray();\r
444                     if (fleet.Count(s => s == 10 || s == 22) == 2)\r
445                         IncrementCount(q861.Count);\r
446                 }\r
447             }\r
448         }\r
449 \r
450         public void InspectBattleResult(dynamic json)\r
451         {\r
452             var rank = json.api_win_rank;\r
453             foreach (var quest in _quests.Values)\r
454             {\r
455                 var count = quest.Count;\r
456                 switch (count.Spec)\r
457                 {\r
458                     case QuestSortie sortie:\r
459                         if (count.Id == 216 && !_boss || sortie.Check(rank, _map, _boss))\r
460                             IncrementCount(count);\r
461                         break;\r
462                     case QuestEnemyType enemyType:\r
463                         var num = enemyType.CountResult(\r
464                             _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));\r
465                         if (num > 0)\r
466                             AddCount(count, num);\r
467                         break;\r
468                 }\r
469             }\r
470             if (_quests.TryGetValue(214, out var ago))\r
471             {\r
472                 var count = ago.Count;\r
473                 if (_boss)\r
474                 {\r
475                     IncrementNowArray(count, 2);\r
476                     if (QuestSortie.CompareRank(rank, "B") <= 0)\r
477                         IncrementNowArray(count, 3);\r
478                 }\r
479                 if (rank == "S")\r
480                     IncrementNowArray(count, 1);\r
481             }\r
482             if (_quests.TryGetValue(249, out var q249))\r
483             {\r
484                 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
485                 {\r
486                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id)\r
487                         .ToArray();\r
488                     if (fleet.Intersect(new[] {62, 63, 64, 265, 266, 268, 319, 192, 194}).Count() == 3)\r
489                         IncrementCount(q249.Count);\r
490                 }\r
491             }\r
492             if (_quests.TryGetValue(257, out var q257))\r
493             {\r
494                 if (_map == 14 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
495                 {\r
496                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
497                         .ToArray();\r
498                     if (fleet[0] == 3 && fleet.Count(s => s == 3) <= 3 && fleet.All(s => s == 2 || s == 3))\r
499                         IncrementCount(q257.Count);\r
500                 }\r
501             }\r
502             if (_quests.TryGetValue(259, out var q259))\r
503             {\r
504                 if (_map == 51 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
505                 {\r
506                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec).ToArray();\r
507                     // ReSharper disable once IdentifierTypo\r
508                     var ctype = new[]\r
509                     {\r
510                         2, // 伊勢型\r
511                         19, // 長門型\r
512                         26, // 扶桑型\r
513                         37 // 大和型\r
514                     };\r
515                     if (fleet.Select(s => s.ShipClass).Count(c => ctype.Contains(c)) == 3 &&\r
516                         fleet.Count(s => s.ShipType == 3) > 0)\r
517                     {\r
518                         IncrementCount(q259.Count);\r
519                     }\r
520                 }\r
521             }\r
522             if (_quests.TryGetValue(264, out var q264))\r
523             {\r
524                 if (_map == 42 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
525                 {\r
526                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec)\r
527                         .ToArray();\r
528                     if (fleet.Count(spec => spec.ShipType == 2) >= 2 &&\r
529                         fleet.Count(spec => spec.IsAircraftCarrier) >= 2)\r
530                         IncrementCount(q264.Count);\r
531                 }\r
532             }\r
533             if (_quests.TryGetValue(266, out var q266))\r
534             {\r
535                 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
536                 {\r
537                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
538                         .ToArray();\r
539                     if (fleet[0] == 2 && fleet.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5}))\r
540                         IncrementCount(q266.Count);\r
541                 }\r
542             }\r
543             if (_quests.TryGetValue(280, out var q280))\r
544             {\r
545                 if (!(_boss && QuestSortie.CompareRank(rank, "S") == 0))\r
546                     return;\r
547                 var count = q280.Count;\r
548                 switch (_map)\r
549                 {\r
550                     case 12:\r
551                         IncrementNowArray(count, 0);\r
552                         break;\r
553                     case 13:\r
554                         IncrementNowArray(count, 1);\r
555                         break;\r
556                     case 14:\r
557                         IncrementNowArray(count, 2);\r
558                         break;\r
559                     case 21:\r
560                         IncrementNowArray(count, 3);\r
561                         break;\r
562                 }\r
563             }\r
564             if (_quests.TryGetValue(854, out var opz) && _boss)\r
565             {\r
566                 var count = opz.Count;\r
567                 switch (_map)\r
568                 {\r
569                     case 24 when QuestSortie.CompareRank(rank, "A") <= 0:\r
570                         IncrementNowArray(count, 0);\r
571                         break;\r
572                     case 61 when QuestSortie.CompareRank(rank, "A") <= 0:\r
573                         IncrementNowArray(count, 1);\r
574                         break;\r
575                     case 63 when QuestSortie.CompareRank(rank, "A") <= 0:\r
576                         IncrementNowArray(count, 2);\r
577                         NeedSave = true;\r
578                         break;\r
579                     case 64 when QuestSortie.CompareRank(rank, "S") <= 0:\r
580                         IncrementNowArray(count, 3);\r
581                         break;\r
582                 }\r
583             }\r
584             if (_quests.TryGetValue(862, out var q862))\r
585             {\r
586                 if (_map == 63 && _boss && QuestSortie.CompareRank(rank, "A") <= 0)\r
587                 {\r
588                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
589                         .ToArray();\r
590                     if (fleet.Count(s => s == 3) >= 2 && fleet.Count(s => s == 16) >= 1)\r
591                         IncrementCount(q862.Count);\r
592                 }\r
593             }\r
594             if (_quests.TryGetValue(873, out var q873))\r
595             {\r
596                 if (_battleInfo.Result.Friend.Main.Count(s => s.NowHp > 0 && s.Spec.ShipType == 3) >= 1 &&\r
597                     _boss && QuestSortie.CompareRank(rank, "A") <= 0)\r
598                 {\r
599                     var count = q873.Count;\r
600                     switch (_map)\r
601                     {\r
602                         case 31:\r
603                             IncrementNowArray(count, 0);\r
604                             break;\r
605                         case 32:\r
606                             IncrementNowArray(count, 1);\r
607                             break;\r
608                         case 33:\r
609                             IncrementNowArray(count, 2);\r
610                             break;\r
611                     }\r
612                 }\r
613             }\r
614             if (_quests.TryGetValue(875, out var q875))\r
615             {\r
616                 if (_map == 54 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
617                 {\r
618                     var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id).ToArray();\r
619                     if (fleet.Contains(543) && fleet.Intersect(new[] {344, 345, 359}).Any())\r
620                         IncrementCount(q875.Count);\r
621                 }\r
622             }\r
623             if (_quests.TryGetValue(888, out var q888))\r
624             {\r
625                 if (!_boss || QuestSortie.CompareRank(rank, "S") != 0)\r
626                     return;\r
627                 var fleet = from ship in _battleInfo.Result.Friend.Main where ship.NowHp > 0 select ship.Spec.Id;\r
628                 var member = new[]\r
629                 {\r
630                     69, 272, 427, // 鳥海\r
631                     61, 264, // 青葉\r
632                     123, 295, 142, // 衣笠\r
633                     59, 262, 416, // 古鷹\r
634                     60, 263, 417, // 加古\r
635                     51, 213, 477, // 天龍\r
636                     115, 293 // 夕張\r
637                 };\r
638                 if (fleet.Intersect(member).Count() < 4)\r
639                     return;\r
640                 var count = q888.Count;\r
641                 switch (_map)\r
642                 {\r
643                     case 51:\r
644                         IncrementNowArray(count, 0);\r
645                         break;\r
646                     case 53:\r
647                         IncrementNowArray(count, 1);\r
648                         break;\r
649                     case 54:\r
650                         IncrementNowArray(count, 2);\r
651                         break;\r
652                 }\r
653             }\r
654             if (_quests.TryGetValue(893, out var q893))\r
655             {\r
656                 if (!_boss || QuestSortie.CompareRank(rank, "S") != 0)\r
657                     return;\r
658                 var count = q893.Count;\r
659                 switch (_map)\r
660                 {\r
661                     case 15:\r
662                         IncrementNowArray(count, 0);\r
663                         break;\r
664                     case 71:\r
665                         IncrementNowArray(count, 1);\r
666                         break;\r
667                     case 72:\r
668                         if (_cell == 7)\r
669                         {\r
670                             IncrementNowArray(count, 2);\r
671                             break;\r
672                         }\r
673                         IncrementNowArray(count, 3);\r
674                         break;\r
675                 }\r
676             }\r
677             if (_quests.TryGetValue(894, out var q894))\r
678             {\r
679                 if (!_boss ||\r
680                     QuestSortie.CompareRank(rank, "S") != 0 ||\r
681                     !_battleInfo.Result.Friend.Main.Any(s => s.Spec.IsAircraftCarrier && s.NowHp > 0))\r
682                     return;\r
683                 var count = q894.Count;\r
684                 switch (_map)\r
685                 {\r
686                     case 13:\r
687                         IncrementNowArray(count, 0);\r
688                         break;\r
689                     case 14:\r
690                         IncrementNowArray(count, 1);\r
691                         break;\r
692                     case 21:\r
693                         IncrementNowArray(count, 2);\r
694                         break;\r
695                     case 22:\r
696                         IncrementNowArray(count, 3);\r
697                         break;\r
698                     case 23:\r
699                         IncrementNowArray(count, 4);\r
700                         break;\r
701                 }\r
702             }\r
703         }\r
704 \r
705         private int _questFleet;\r
706 \r
707         public void StartPractice(string request)\r
708         {\r
709             var values = HttpUtility.ParseQueryString(request);\r
710             _questFleet = int.Parse(values["api_deck_id"]) - 1;\r
711         }\r
712 \r
713         public void InspectPracticeResult(dynamic json)\r
714         {\r
715             foreach (var quest in _quests.Values)\r
716             {\r
717                 var count = quest.Count;\r
718                 if (!(count.Spec is QuestPractice practice))\r
719                     continue;\r
720                 if (practice.Check(json.api_win_rank))\r
721                     IncrementCount(count);\r
722             }\r
723             if (_quests.TryGetValue(318, out var q318))\r
724             {\r
725                 if (_questFleet == 0 && QuestSortie.CompareRank(json.api_win_rank, "B") <= 0 &&\r
726                     _battleInfo.Result.Friend.Main.Count(s => s.Spec.ShipType == 3) >= 2)\r
727                 {\r
728                     IncrementCount(q318.Count);\r
729                 }\r
730             }\r
731             if (_quests.TryGetValue(330, out var q330))\r
732             {\r
733                 var fleet = _battleInfo.Result.Friend.Main;\r
734                 if (QuestSortie.CompareRank(json.api_win_rank, "B") <= 0 &&\r
735                     fleet.Count(s => s.Spec.IsAircraftCarrier) >= 2 &&\r
736                     fleet.Count(s => s.Spec.ShipType == 2) >= 2 &&\r
737                     fleet[0].Spec.IsAircraftCarrier)\r
738                 {\r
739                     IncrementCount(q330.Count);\r
740                 }\r
741             }\r
742         }\r
743 \r
744         private readonly int[] _missionId = new int[ShipInfo.FleetCount];\r
745 \r
746         public void InspectDeck(dynamic json)\r
747         {\r
748             foreach (var entry in json)\r
749                 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];\r
750         }\r
751 \r
752         public void InspectMissionResult(string request, dynamic json)\r
753         {\r
754             var values = HttpUtility.ParseQueryString(request);\r
755             var deck = int.Parse(values["api_deck_id"]);\r
756             if ((int)json.api_clear_result == 0)\r
757                 return;\r
758             var mid = _missionId[deck - 1];\r
759             foreach (var quest in _quests.Values)\r
760             {\r
761                 var count = quest.Count;\r
762                 if (!(count.Spec is QuestMission mission))\r
763                     continue;\r
764                 if (mission.Check(mid))\r
765                     IncrementCount(count);\r
766             }\r
767             if (_quests.TryGetValue(426, out var q426))\r
768             {\r
769                 var count = q426.Count;\r
770                 switch (mid)\r
771                 {\r
772                     case 3:\r
773                         IncrementNowArray(count, 0);\r
774                         break;\r
775                     case 4:\r
776                         IncrementNowArray(count, 1);\r
777                         break;\r
778                     case 5:\r
779                         IncrementNowArray(count, 2);\r
780                         break;\r
781                     case 10:\r
782                         IncrementNowArray(count, 3);\r
783                         break;\r
784                 }\r
785             }\r
786             if (_quests.TryGetValue(428, out var q428))\r
787             {\r
788                 var count = q428.Count;\r
789                 switch (mid)\r
790                 {\r
791                     case 4:\r
792                         IncrementNowArray(count, 0);\r
793                         break;\r
794                     case 101:\r
795                         IncrementNowArray(count, 1);\r
796                         break;\r
797                     case 102:\r
798                         IncrementNowArray(count, 2);\r
799                         break;\r
800                 }\r
801             }\r
802         }\r
803 \r
804         private void IncrementCount(QuestCount count)\r
805         {\r
806             count.Now++;\r
807             NeedSave = true;\r
808         }\r
809 \r
810         private void AddCount(QuestCount count, int value)\r
811         {\r
812             count.Now += value;\r
813             NeedSave = true;\r
814         }\r
815 \r
816         private void IncrementCount(int id)\r
817         {\r
818             AddCount(id, 1);\r
819         }\r
820 \r
821         private void AddCount(int id, int value)\r
822         {\r
823             if (_quests.TryGetValue(id, out var quest))\r
824             {\r
825                 quest.Count.Now += value;\r
826                 NeedSave = true;\r
827             }\r
828         }\r
829 \r
830         private void IncrementNowArray(QuestCount count, int n)\r
831         {\r
832             count.NowArray[n]++;\r
833             NeedSave = true;\r
834         }\r
835 \r
836         public void CountNyukyo() => IncrementCount(503);\r
837 \r
838         public void CountCharge() => IncrementCount(504);\r
839 \r
840         public void CountCreateItem()\r
841         {\r
842             IncrementCount(605);\r
843             IncrementCount(607);\r
844         }\r
845 \r
846         public void CountCreateShip()\r
847         {\r
848             IncrementCount(606);\r
849             IncrementCount(608);\r
850         }\r
851 \r
852         public void InspectDestroyShip(string request)\r
853         {\r
854             AddCount(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);\r
855         }\r
856 \r
857         public void CountRemodelSlot() => IncrementCount(619);\r
858 \r
859         public void InspectDestroyItem(string request, dynamic json)\r
860         {\r
861             var values = HttpUtility.ParseQueryString(request);\r
862             var items = values["api_slotitem_ids"].Split(',')\r
863                 .Select(id => _itemInfo.GetStatus(int.Parse(id)).Spec).ToArray();\r
864             IncrementCount(613); // 613: 資源の再利用\r
865             foreach (var quest in _quests.Values)\r
866             {\r
867                 var count = quest.Count;\r
868                 if (!(count.Spec is QuestDestroyItem destroy))\r
869                     continue;\r
870                 if (destroy.Count(count, items))\r
871                     NeedSave = true;\r
872             }\r
873             if (_quests.TryGetValue(680, out var q680))\r
874             {\r
875                 q680.Count.NowArray[0] += items.Count(spec => spec.Type == 21);\r
876                 q680.Count.NowArray[1] += items.Count(spec => spec.Type == 12 || spec.Type == 13);\r
877                 NeedSave = true;\r
878             }\r
879         }\r
880 \r
881         public void InspectPowerUp(dynamic json)\r
882         {\r
883             if ((int)json.api_powerup_flag == 0)\r
884                 return;\r
885             foreach (var quest in _quests.Values)\r
886             {\r
887                 var count = quest.Count;\r
888                 if (!(count.Spec is QuestPowerUp))\r
889                     continue;\r
890                 IncrementCount(count);\r
891             }\r
892         }\r
893 \r
894         public void InspectStop(string request)\r
895         {\r
896             var values = HttpUtility.ParseQueryString(request);\r
897             _quests.Remove(int.Parse(values["api_quest_id"]));\r
898             NeedSave = true;\r
899         }\r
900 \r
901         public void InspectClearItemGet(string request)\r
902         {\r
903             var values = HttpUtility.ParseQueryString(request);\r
904             var id = int.Parse(values["api_quest_id"]);\r
905             _countList.Remove(id);\r
906             _quests.Remove(id);\r
907             NeedSave = true;\r
908         }\r
909 \r
910         public bool NeedSave { get; private set; }\r
911 \r
912         public void SaveState(Status status)\r
913         {\r
914             NeedSave = false;\r
915             status.QuestLastReset = _lastReset;\r
916             if (_quests != null)\r
917                 status.QuestList = _quests.Values.ToArray();\r
918             if (_countList != null)\r
919                 status.QuestCountList = _countList.CountList.ToArray();\r
920         }\r
921 \r
922         public void LoadState(Status status)\r
923         {\r
924             _lastReset = status.QuestLastReset;\r
925             if (status.QuestCountList != null)\r
926                 _countList.CountList = status.QuestCountList;\r
927             if (status.QuestList != null)\r
928             {\r
929                 _quests.Clear();\r
930                 foreach (var q in status.QuestList)\r
931                     AddQuest(q, false);\r
932             }\r
933         }\r
934     }\r
935 }