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