OSDN Git Service

「十八駆」演習!のカウンターを実装する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / QuestCounter.cs
1 // Copyright (C) 2019 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.Linq;\r
18 using System.Xml.Serialization;\r
19 using KancolleSniffer.Util;\r
20 \r
21 namespace KancolleSniffer.Model\r
22 {\r
23     public class QuestCount\r
24     {\r
25         public int Id { get; set; }\r
26         public int Now { get; set; }\r
27         public int[] NowArray { get; set; }\r
28 \r
29         [XmlIgnore]\r
30         public QuestSpec Spec { get; set; }\r
31 \r
32         public bool AdjustCount(int progress)\r
33         {\r
34             if (!Spec.AdjustCount)\r
35                 return false;\r
36             if (NowArray != null)\r
37             {\r
38                 if (progress != 100)\r
39                     return false;\r
40                 NowArray = NowArray.Zip(Spec.MaxArray, Math.Max).ToArray();\r
41                 return true;\r
42             }\r
43             var next = 0;\r
44             switch (progress)\r
45             {\r
46                 case 0:\r
47                     next = 50;\r
48                     break;\r
49                 case 50:\r
50                     next = 80;\r
51                     break;\r
52                 case 80:\r
53                     next = 100;\r
54                     break;\r
55                 case 100:\r
56                     next = 100000;\r
57                     break;\r
58             }\r
59             var now = Now + Spec.Shift;\r
60             var max = Spec.Max + Spec.Shift;\r
61             var low = (int)Math.Ceiling(max * progress / 100.0);\r
62             if (low >= max && progress != 100)\r
63                 low = max - 1;\r
64             var high = (int)Math.Ceiling(max * next / 100.0);\r
65             if (now < low)\r
66             {\r
67                 Now = low - Spec.Shift;\r
68                 return true;\r
69             }\r
70             if (now >= high)\r
71             {\r
72                 Now = high - 1 - Spec.Shift;\r
73                 return true;\r
74             }\r
75             return false;\r
76         }\r
77 \r
78         public override string ToString()\r
79         {\r
80             if (Id == 280 || Id == 284 || Id == 426 || Id == 845 || Id == 854 || Id == 872 || Id == 873 || Id == 888 || Id == 894)\r
81                 return $"{NowArray.Count(n => n >= 1)}/{Spec.MaxArray.Length}";\r
82             return NowArray != null\r
83                 ? string.Join(" ", NowArray.Zip(Spec.MaxArray, (n, m) => $"{n}/{m}"))\r
84                 : $"{Now}/{Spec.Max}";\r
85         }\r
86 \r
87         public QuestCount Clone()\r
88         {\r
89             var clone = (QuestCount)MemberwiseClone();\r
90             if (NowArray != null)\r
91                 clone.NowArray = (int[])NowArray.Clone();\r
92             return clone;\r
93         }\r
94 \r
95         public bool Equals(QuestCount other)\r
96         {\r
97             if (Id != other.Id)\r
98                 return false;\r
99             if (NowArray == null)\r
100                 return Now == other.Now;\r
101             return NowArray.SequenceEqual(other.NowArray);\r
102         }\r
103 \r
104         public string ToToolTip()\r
105         {\r
106             switch (Id)\r
107             {\r
108                 case 280:\r
109                     return string.Join(" ",\r
110                         new[] {"1-2", "1-3", "1-4", "2-1"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
111                             .Where(s => !string.IsNullOrEmpty(s)));\r
112                 case 284:\r
113                     return string.Join(" ",\r
114                         new[] {"1-4", "2-1", "2-2", "2-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
115                             .Where(s => !string.IsNullOrEmpty(s)));\r
116                 case 426:\r
117                     return string.Join(" ",\r
118                         new[] {"警備任務", "対潜警戒任務", "海上護衛任務", "強硬偵察任務"}\r
119                             .Zip(NowArray, (mission, n) => n >= 1 ? mission : "")\r
120                             .Where(s => !string.IsNullOrEmpty(s)));\r
121                 case 428:\r
122                     return string.Join(" ",\r
123                         new[] {"対潜警戒任務", "海峡警備行動", "長時間対潜警戒"}.Zip(NowArray, (mission, n) => n >= 1 ? mission + n : "")\r
124                             .Where(s => !string.IsNullOrEmpty(s)));\r
125                 case 845:\r
126                     return string.Join(" ",\r
127                         new[] {"4-1", "4-2", "4-3", "4-4", "4-5"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
128                             .Where(s => !string.IsNullOrEmpty(s)));\r
129                 case 854:\r
130                     return string.Join(" ",\r
131                         new[] {"2-4", "6-1", "6-3", "6-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
132                             .Where(s => !string.IsNullOrEmpty(s)));\r
133                 case 872:\r
134                     return string.Join(" ",\r
135                         new[] {"7-2M", "5-5", "6-2", "6-5"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
136                             .Where(s => !string.IsNullOrEmpty(s)));\r
137                 case 873:\r
138                     return string.Join(" ",\r
139                         new[] {"3-1", "3-2", "3-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
140                             .Where(s => !string.IsNullOrEmpty(s)));\r
141                 case 888:\r
142                     return string.Join(" ",\r
143                         new[] {"5-1", "5-3", "5-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
144                             .Where(s => !string.IsNullOrEmpty(s)));\r
145                 case 688:\r
146                     return string.Join(" ",\r
147                         new[] {"艦戦", "艦爆", "艦攻", "水偵"}.Zip(NowArray, (type, n) => n >= 1 ? type + n : "")\r
148                             .Where(s => !string.IsNullOrEmpty(s)));\r
149                 case 893:\r
150                     return string.Join(" ",\r
151                         new[] {"1-5", "7-1", "7-2G", "7-2M"}.Zip(NowArray, (map, n) => n >= 1 ? $"{map}:{n}" : "")\r
152                             .Where(s => !string.IsNullOrEmpty(s)));\r
153                 case 894:\r
154                     return string.Join(" ",\r
155                         new[] {"1-3", "1-4", "2-1", "2-2", "2-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
156                             .Where(s => !string.IsNullOrEmpty(s)));\r
157             }\r
158             return "";\r
159         }\r
160 \r
161         public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ??\r
162                                Spec.Max != 0 && Now >= Spec.Max;\r
163     }\r
164 \r
165     public class QuestCounter\r
166     {\r
167         private readonly QuestInfo _questInfo;\r
168         private readonly ItemInfo _itemInfo;\r
169         private readonly BattleInfo _battleInfo;\r
170         private readonly SortedDictionary<int, QuestStatus> _quests;\r
171         private int _map;\r
172         private bool _boss;\r
173 \r
174         private class ResultShipSpecs\r
175         {\r
176             public ResultShipSpecs(BattleInfo battleInfo)\r
177             {\r
178                 Specs = battleInfo.Result?.Friend.Main.Where(s => s.NowHp > 0).Select(ship => ship.Spec).ToArray() ??\r
179                         new ShipSpec[0];\r
180                 Ids = Specs.Select(spec => spec.Id).ToArray();\r
181                 Types = Specs.Select(spec => spec.ShipType).ToArray();\r
182                 Classes = Specs.Select(spec => spec.ShipClass).ToArray();\r
183                 Flagship = Specs.FirstOrDefault();\r
184                 FlagshipType = Types.FirstOrDefault();\r
185             }\r
186 \r
187             public ShipSpec[] Specs { get; }\r
188             public int[] Ids { get; }\r
189             public int[] Types { get; }\r
190             public int[] Classes { get; }\r
191             public ShipSpec Flagship { get; }\r
192             public int FlagshipType { get; }\r
193         }\r
194 \r
195         public QuestCounter(QuestInfo questInfo, ItemInfo itemInfo, BattleInfo battleInfo)\r
196         {\r
197             _questInfo = questInfo;\r
198             _quests = questInfo.QuestDictionary;\r
199             _itemInfo = itemInfo;\r
200             _battleInfo = battleInfo;\r
201         }\r
202 \r
203         private bool NeedSave\r
204         {\r
205             set => _questInfo.NeedSave = value;\r
206         }\r
207 \r
208         public void InspectMapStart(dynamic json)\r
209         {\r
210             if (_quests.TryGetValue(214, out var ago)) // あ号\r
211                 ago.Count.NowArray[0]++;\r
212             InspectMapNext(json);\r
213         }\r
214 \r
215         public void InspectMapNext(dynamic json)\r
216         {\r
217             _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;\r
218             if (_map == 72)\r
219             {\r
220                 var cell = json.api_no() ? (int)json.api_no : 0;\r
221                 _map *= 10;\r
222                 if (cell == 7)\r
223                     _map++;\r
224                 if (cell == 15)\r
225                     _map += 2;\r
226             }\r
227             _boss = (int)json.api_event_id == 5;\r
228 \r
229             if (_quests.TryGetValue(861, out var q861) && _map == 16 && (int)json.api_event_id == 8)\r
230             {\r
231                 if (new ResultShipSpecs(_battleInfo).Types.Count(s => s == 10 || s == 22) == 2)\r
232                     Increment(q861.Count);\r
233             }\r
234         }\r
235 \r
236         public void InspectBattleResult(dynamic json)\r
237         {\r
238             var rank = json.api_win_rank;\r
239             foreach (var count in _quests.Values.Select(q => q.Count))\r
240             {\r
241                 switch (count.Spec)\r
242                 {\r
243                     case QuestSortie sortie:\r
244                         if (!FleetCheck(count.Id))\r
245                             continue;\r
246                         if (!_boss && count.Id == 216)\r
247                         {\r
248                             Increment(count);\r
249                             continue;\r
250                         }\r
251                         if (sortie.Count(count, rank, _map, _boss))\r
252                             NeedSave = true;\r
253                         continue;\r
254                     case QuestEnemyType enemyType:\r
255                         var num = enemyType.CountResult(\r
256                             _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));\r
257                         if (num > 0)\r
258                             Add(count, num);\r
259                         continue;\r
260                 }\r
261                 if (count.Id == 214)\r
262                     CountAgo(count, rank);\r
263             }\r
264         }\r
265 \r
266         private void CountAgo(QuestCount count, string rank)\r
267         {\r
268             if (QuestSortie.CompareRank(rank, "S") == 0)\r
269                 IncrementNth(count, 1);\r
270             if (!_boss)\r
271                 return;\r
272             IncrementNth(count, 2);\r
273             if (QuestSortie.CompareRank(rank, "B") <= 0)\r
274                 IncrementNth(count, 3);\r
275         }\r
276 \r
277         private bool FleetCheck(int id)\r
278         {\r
279             var specs = new ResultShipSpecs(_battleInfo);\r
280             switch (id)\r
281             {\r
282                 case 249:\r
283                     return specs.Ids.Intersect(new[] {62, 63, 64, 265, 266, 268, 319, 192, 194}).Count() == 3;\r
284                 case 257:\r
285                     return specs.FlagshipType == 3 && specs.Types.Count(s => s == 3) <= 3 &&\r
286                            specs.Types.All(s => s == 2 || s == 3);\r
287                 case 259:\r
288                     return specs.Types.Count(type => type == 3) > 0 && specs.Classes.Count(c => new[]\r
289                     {\r
290                         2, // 伊勢型\r
291                         19, // 長門型\r
292                         26, // 扶桑型\r
293                         37 // 大和型\r
294                     }.Contains(c)) == 3;\r
295                 case 264:\r
296                     return specs.Types.Count(type => type == 2) >= 2 &&\r
297                            specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2;\r
298                 case 266:\r
299                     return specs.FlagshipType == 2 &&\r
300                            specs.Types.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5});\r
301                 case 280:\r
302                 case 284:\r
303                     return specs.Types.Count(type => type == 1 || type == 2) >= 3 &&\r
304                            specs.Types.Intersect(new[] {3, 4, 7, 21}).Any();\r
305                 case 862:\r
306                     return specs.Types.Count(s => s == 3) >= 2 && specs.Types.Count(s => s == 16) >= 1;\r
307                 case 873:\r
308                     return specs.Types.Count(type => type == 3) >= 1;\r
309                 case 875:\r
310                     return specs.Ids.Contains(543) &&\r
311                            specs.Ids.Intersect(new[]\r
312                            {\r
313                                344, // 朝霜改\r
314                                345, // 高波改\r
315                                359, // 沖波改\r
316                                578 // 朝霜改二\r
317                            }).Any();\r
318                 case 888:\r
319                     return specs.Ids.Intersect(new[]\r
320                     {\r
321                         69, 272, 427, // 鳥海\r
322                         61, 264, // 青葉\r
323                         123, 295, 142, // 衣笠\r
324                         59, 262, 416, // 古鷹\r
325                         60, 263, 417, // 加古\r
326                         51, 213, 477, // 天龍\r
327                         115, 293 // 夕張\r
328                     }).Count() >= 4;\r
329                 case 894:\r
330                     return specs.Specs.Any(spec => spec.IsAircraftCarrier);\r
331                 case 318:\r
332                     return specs.Types.Count(type => type == 3) >= 2;\r
333                 case 330:\r
334                     return specs.Flagship.IsAircraftCarrier &&\r
335                            specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2 &&\r
336                            specs.Types.Count(type => type == 2) >= 2;\r
337                 case 337:\r
338                     return specs.Ids.Intersect(new[]\r
339                     {\r
340                         17, 225, 566, // 陽炎\r
341                         18, 226, 567, // 不知火\r
342                         48, 198, 252, // 霰\r
343                         49, 253, 464 // 霞\r
344                     }).Count() == 4;\r
345                 default:\r
346                     return true;\r
347             }\r
348         }\r
349 \r
350         private int _questFleet;\r
351 \r
352         public void StartPractice(string request)\r
353         {\r
354             var values = HttpUtility.ParseQueryString(request);\r
355             _questFleet = int.Parse(values["api_deck_id"]) - 1;\r
356         }\r
357 \r
358         public void InspectPracticeResult(dynamic json)\r
359         {\r
360             foreach (var count in _quests.Values.Select(q => q.Count))\r
361             {\r
362                 if (!FleetCheck(count.Id))\r
363                     continue;\r
364                 if (count.Id == 318 && _questFleet != 0)\r
365                     continue;\r
366                 if (!(count.Spec is QuestPractice practice))\r
367                     continue;\r
368                 if (practice.Check(json.api_win_rank))\r
369                     Increment(count);\r
370             }\r
371         }\r
372 \r
373         private readonly int[] _missionId = new int[ShipInfo.FleetCount];\r
374 \r
375         public void InspectDeck(dynamic json)\r
376         {\r
377             foreach (var entry in json)\r
378                 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];\r
379         }\r
380 \r
381         public void InspectMissionResult(string request, dynamic json)\r
382         {\r
383             var values = HttpUtility.ParseQueryString(request);\r
384             var deck = int.Parse(values["api_deck_id"]);\r
385             if ((int)json.api_clear_result == 0)\r
386                 return;\r
387             var mid = _missionId[deck - 1];\r
388             foreach (var count in _quests.Values.Select(q => q.Count))\r
389             {\r
390                 if (!(count.Spec is QuestMission mission))\r
391                     continue;\r
392                 if (mission.Count(count, mid))\r
393                     NeedSave = true;\r
394             }\r
395         }\r
396 \r
397         public void CountNyukyo() => Increment(503);\r
398 \r
399         public void CountCharge() => Increment(504);\r
400 \r
401         public void InspectCreateItem(string request)\r
402         {\r
403             var values = HttpUtility.ParseQueryString(request);\r
404             var count = values["api_multiple_flag"] == "1" ? 3 : 1;\r
405             Add(605, count);\r
406             Add(607, count);\r
407         }\r
408 \r
409         public void CountCreateShip()\r
410         {\r
411             Increment(606);\r
412             Increment(608);\r
413         }\r
414 \r
415         public void InspectDestroyShip(string request)\r
416         {\r
417             Add(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);\r
418         }\r
419 \r
420         public void CountRemodelSlot() => Increment(619);\r
421 \r
422         public void InspectDestroyItem(string request, dynamic json)\r
423         {\r
424             var values = HttpUtility.ParseQueryString(request);\r
425             var items = values["api_slotitem_ids"].Split(',')\r
426                 .Select(id => _itemInfo.GetStatus(int.Parse(id)).Spec).ToArray();\r
427             Increment(613); // 613: 資源の再利用\r
428             foreach (var quest in _quests.Values)\r
429             {\r
430                 var count = quest.Count;\r
431                 if (count.Spec is QuestDestroyItem destroy)\r
432                 {\r
433                     if (destroy.Count(count, items))\r
434                         NeedSave = true;\r
435                     continue;\r
436                 }\r
437                 if (quest.Id == 680)\r
438                 {\r
439                     count.NowArray[0] += items.Count(spec => spec.Type == 21);\r
440                     count.NowArray[1] += items.Count(spec => spec.Type == 12 || spec.Type == 13);\r
441                     NeedSave = true;\r
442                 }\r
443             }\r
444         }\r
445 \r
446         public void InspectPowerUp(dynamic json)\r
447         {\r
448             if ((int)json.api_powerup_flag == 0)\r
449                 return;\r
450             foreach (var quest in _quests.Values)\r
451             {\r
452                 var count = quest.Count;\r
453                 if (!(count.Spec is QuestPowerUp))\r
454                     continue;\r
455                 Increment(count);\r
456             }\r
457         }\r
458 \r
459         public void Increment(QuestCount count)\r
460         {\r
461             Add(count, 1);\r
462         }\r
463 \r
464         public void Add(QuestCount count, int value)\r
465         {\r
466             count.Now += value;\r
467             NeedSave = true;\r
468         }\r
469 \r
470         public void Increment(int id)\r
471         {\r
472             Add(id, 1);\r
473         }\r
474 \r
475         public void Add(int id, int value)\r
476         {\r
477             if (!_quests.TryGetValue(id, out var quest))\r
478                 return;\r
479             quest.Count.Now += value;\r
480             NeedSave = true;\r
481         }\r
482 \r
483         public void IncrementNth(QuestCount count, int n)\r
484         {\r
485             count.NowArray[n]++;\r
486             NeedSave = true;\r
487         }\r
488     }\r
489 }