OSDN Git Service

バージョン12.11の準備
[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             return Spec.MaxArray != null && Spec.MaxArray.All(x => x == 1)\r
81                 ? string.Join("\u200a", NowArray.Select(n => (n % 10).ToString()))\r
82                 : Spec.MaxArray != 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         private static string MapString(int map)\r
105         {\r
106             return map switch\r
107             {\r
108                 721 => "7-2G",\r
109                 722 => "7-2M",\r
110                 _ => $"{map / 10}-{map % 10}"\r
111             };\r
112         }\r
113 \r
114         public string ToToolTip()\r
115         {\r
116             if (NowArray == null)\r
117                 return "";\r
118             switch (Spec)\r
119             {\r
120                 case QuestSortie sortie when sortie.Maps != null && sortie.MaxArray != null:\r
121                     return string.Join(" ", sortie.Maps.Zip(NowArray, (map, n) => $"{MapString(map)}:{n}"));\r
122                 case QuestMission mission when mission.Ids != null:\r
123                     return string.Join(" ", mission.Names.Zip(NowArray, (name, n) => $"{name}{n}"));\r
124                 default:\r
125                     return string.Join(" ", (Id switch\r
126                     {\r
127                         688 => new[] {"艦戦", "艦爆", "艦攻", "水偵"},\r
128                         _ => new string[0]\r
129                     }).Zip(NowArray, (entry, n) => $"{entry}{n}"));\r
130             }\r
131         }\r
132 \r
133         public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ??\r
134                                Spec.Max != 0 && Now >= Spec.Max;\r
135     }\r
136 \r
137     public class QuestCounter\r
138     {\r
139         private readonly QuestInfo _questInfo;\r
140         private readonly ItemInventory _itemInventory;\r
141         private readonly ShipInventory _shipInventory;\r
142         private readonly BattleInfo _battleInfo;\r
143         private readonly SortedDictionary<int, QuestStatus> _quests;\r
144         private int _map;\r
145         private bool _boss;\r
146 \r
147         private class ResultShipSpecs\r
148         {\r
149             public ShipSpec[] Specs { get; }\r
150             public NameChecker Names { get; }\r
151             public int[] Types { get; }\r
152             public int[] Classes { get; }\r
153             public ShipSpec Flagship { get; }\r
154             public int FlagshipType { get; }\r
155 \r
156             public class NameChecker\r
157             {\r
158                 private readonly string[] _names;\r
159 \r
160                 public NameChecker(ShipSpec[] specs)\r
161                 {\r
162                     _names = specs.Select(spec => spec.Name).ToArray();\r
163                 }\r
164 \r
165                 public bool Contains(string demand)\r
166                 {\r
167                     return _names.Any(name => name.StartsWith(demand));\r
168                 }\r
169 \r
170                 public int Count(params string[] demands)\r
171                 {\r
172                     return demands.Sum(demand => _names.Count(name => name.StartsWith(demand)));\r
173                 }\r
174             }\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                 Names = new NameChecker(Specs);\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 \r
188         public QuestCounter(QuestInfo questInfo, ItemInventory itemInventory, ShipInventory shipInventory, BattleInfo battleInfo)\r
189         {\r
190             _questInfo = questInfo;\r
191             _quests = questInfo.QuestDictionary;\r
192             _itemInventory = itemInventory;\r
193             _shipInventory = shipInventory;\r
194             _battleInfo = battleInfo;\r
195         }\r
196 \r
197         private bool NeedSave\r
198         {\r
199             set => _questInfo.NeedSave = value;\r
200         }\r
201 \r
202         public void InspectMapStart(dynamic json)\r
203         {\r
204             if (_quests.TryGetValue(214, out var ago)) // あ号\r
205                 ago.Count.NowArray[0]++;\r
206             InspectMapNext(json);\r
207         }\r
208 \r
209         public void InspectMapNext(dynamic json)\r
210         {\r
211             _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;\r
212             var cell = json.api_no() ? (int)json.api_no : 0;\r
213             if (_map == 72)\r
214             {\r
215                 if (cell == 7)\r
216                     _map = 721;\r
217                 else if (cell == 15)\r
218                     _map = 722;\r
219             }\r
220             if (_map == 73)\r
221             {\r
222                 switch (cell)\r
223                 {\r
224                     case 5:\r
225                     case 8:\r
226                         _map = 731;\r
227                         break;\r
228                     case 18:\r
229                     case 23:\r
230                     case 24:\r
231                     case 25:\r
232                         _map = 732;\r
233                         break;\r
234                 }\r
235             }\r
236             _boss = (int)json.api_event_id == 5;\r
237 \r
238             if (_map != 16 || (int)json.api_event_id != 8)\r
239                 return;\r
240             foreach (var count in _quests.Values.Select(q => q.Count))\r
241             {\r
242                 if (!(count.Spec is QuestSortie sortie) || sortie.Maps == null)\r
243                     continue;\r
244                 if (!FleetCheck(count.Id))\r
245                     continue;\r
246                 if (sortie.Count(count, "S", _map, true))\r
247                     NeedSave = true;\r
248             }\r
249         }\r
250 \r
251         public void InspectBattleResult(dynamic json)\r
252         {\r
253             var rank = json.api_win_rank;\r
254             foreach (var count in _quests.Values.Select(q => q.Count))\r
255             {\r
256                 switch (count.Spec)\r
257                 {\r
258                     case QuestSortie sortie:\r
259                         if (!FleetCheck(count.Id))\r
260                             continue;\r
261                         if (!_boss && count.Id == 216)\r
262                         {\r
263                             Increment(count);\r
264                             continue;\r
265                         }\r
266                         if (sortie.Count(count, rank, _map, _boss))\r
267                             NeedSave = true;\r
268                         continue;\r
269                     case QuestEnemyType enemyType:\r
270                         var num = enemyType.CountResult(\r
271                             _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));\r
272                         if (num > 0)\r
273                             Add(count, num);\r
274                         continue;\r
275                 }\r
276                 if (count.Id == 214)\r
277                     CountAgo(count, rank);\r
278             }\r
279         }\r
280 \r
281         private void CountAgo(QuestCount count, string rank)\r
282         {\r
283             if (QuestSortie.CompareRank(rank, "S") == 0)\r
284                 IncrementNth(count, 1);\r
285             if (!_boss)\r
286                 return;\r
287             IncrementNth(count, 2);\r
288             if (QuestSortie.CompareRank(rank, "B") <= 0)\r
289                 IncrementNth(count, 3);\r
290         }\r
291 \r
292         private bool FleetCheck(int id)\r
293         {\r
294             var specs = new ResultShipSpecs(_battleInfo);\r
295             switch (id)\r
296             {\r
297                 case 249:\r
298                     return specs.Names.Count("妙高", "那智", "羽黒") == 3;\r
299                 case 257:\r
300                     return specs.FlagshipType == 3 && specs.Types.Count(s => s == 3) <= 3 &&\r
301                            specs.Types.All(s => s == 2 || s == 3);\r
302                 case 259:\r
303                     return specs.Types.Count(type => type == 3) > 0 && specs.Classes.Count(c => new[]\r
304                     {\r
305                         2, // 伊勢型\r
306                         19, // 長門型\r
307                         26, // 扶桑型\r
308                         37 // 大和型\r
309                     }.Contains(c)) == 3;\r
310                 case 264:\r
311                     return specs.Types.Count(type => type == 2) >= 2 &&\r
312                            specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2;\r
313                 case 266:\r
314                     return specs.FlagshipType == 2 &&\r
315                            specs.Types.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5});\r
316                 case 280:\r
317                 case 284:\r
318                     return specs.Types.Count(type => type == 1 || type == 2) >= 3 &&\r
319                            specs.Types.Intersect(new[] {3, 4, 7, 21}).Any();\r
320                 case 840:\r
321                     return new[] {3, 4, 7, 21}.Contains(specs.FlagshipType) &&\r
322                            specs.Types.Count(type => type == 2 || type == 1) >= 3;\r
323                 case 861:\r
324                     return specs.Types.Count(s => s == 10 || s == 22) == 2;\r
325                 case 862:\r
326                     return specs.Types.Count(s => s == 3) >= 2 && specs.Types.Count(s => s == 16) >= 1;\r
327                 case 873:\r
328                     return specs.Types.Count(type => type == 3) >= 1;\r
329                 case 875:\r
330                     return specs.Names.Contains("長波改二") &&\r
331                            specs.Names.Count("朝霜改", "高波改", "沖波改") > 0;\r
332                 case 888:\r
333                     return specs.Names.Count("鳥海", "青葉", "衣笠", "加古", "古鷹", "天龍", "夕張") >= 4;\r
334                 case 894:\r
335                     return specs.Specs.Any(spec => spec.IsAircraftCarrier);\r
336                 case 903:\r
337                     return specs.Flagship.Name.StartsWith("夕張改二") &&\r
338                            (specs.Names.Count("睦月", "如月", "弥生", "卯月", "菊月", "望月") >= 2 || specs.Names.Contains("由良改二"));\r
339                 case 904:\r
340                     return specs.Names.Count("綾波改二", "敷波改二") == 2;\r
341                 case 905:\r
342                     return specs.Types.Count(type => type == 1) >= 3 && specs.Types.Length <= 5;\r
343                 case 912:\r
344                     return specs.Flagship.Name.StartsWith("明石") && specs.Types.Count(type => type == 2) >= 3;\r
345                 case 914:\r
346                     return specs.Types.Count(type => type == 5) >= 3 && specs.Types.Count(type => type == 2) >= 1;\r
347                 case 928:\r
348                     return specs.Names.Count("羽黒", "足柄", "妙高", "高雄", "神風") >= 2;\r
349                 case 318:\r
350                     return specs.Types.Count(type => type == 3) >= 2;\r
351                 case 329:\r
352                     return specs.Types.Count(type => type == 2 || type == 1) >= 2;\r
353                 case 330:\r
354                     return specs.Flagship.IsAircraftCarrier &&\r
355                            specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2 &&\r
356                            specs.Types.Count(type => type == 2) >= 2;\r
357                 case 337:\r
358                     return specs.Names.Count("陽炎", "不知火", "霰", "霞") == 4;\r
359                 case 339:\r
360                     return specs.Names.Count("磯波", "浦波", "綾波", "敷波") == 4;\r
361                 case 342:\r
362                     var t12 = specs.Types.Count(type => type == 1 || type == 2);\r
363                     return t12 >= 4 || t12 >= 3 && specs.Types.Intersect(new[] {3, 4, 7, 21}).Any();\r
364                 case 345:\r
365                     return specs.Names.Count("Warspite", "金剛", "Ark Royal", "Nelson", "Jervis", "Janus") >= 4;\r
366                 case 346:\r
367                     return specs.Names.Count("夕雲改二", "巻雲改二", "風雲改二", "秋雲改二") == 4;\r
368                 case 348:\r
369                     return new[] {3, 21}.Contains(specs.FlagshipType) &&\r
370                            specs.Types.Skip(1).Count(type => new[] {3, 4, 21}.Contains(type)) >= 2 &&\r
371                            specs.Types.Count(type => type == 2) >= 2;\r
372                 default:\r
373                     return true;\r
374             }\r
375         }\r
376 \r
377         private int _questFleet;\r
378 \r
379         public void StartPractice(string request)\r
380         {\r
381             var values = HttpUtility.ParseQueryString(request);\r
382             _questFleet = int.Parse(values["api_deck_id"]) - 1;\r
383         }\r
384 \r
385         public void InspectPracticeResult(dynamic json)\r
386         {\r
387             foreach (var count in _quests.Values.Select(q => q.Count))\r
388             {\r
389                 if (!FleetCheck(count.Id))\r
390                     continue;\r
391                 if (count.Id == 318 && _questFleet != 0)\r
392                     continue;\r
393                 if (!(count.Spec is QuestPractice practice))\r
394                     continue;\r
395                 if (practice.Check(json.api_win_rank))\r
396                     Increment(count);\r
397             }\r
398         }\r
399 \r
400         private readonly int[] _missionId = new int[ShipInfo.FleetCount];\r
401 \r
402         public void InspectDeck(dynamic json)\r
403         {\r
404             foreach (var entry in json)\r
405                 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];\r
406         }\r
407 \r
408         public void InspectMissionResult(string request, dynamic json)\r
409         {\r
410             var values = HttpUtility.ParseQueryString(request);\r
411             var deck = int.Parse(values["api_deck_id"]);\r
412             if ((int)json.api_clear_result == 0)\r
413                 return;\r
414             var mid = _missionId[deck - 1];\r
415             foreach (var count in _quests.Values.Select(q => q.Count))\r
416             {\r
417                 if (!(count.Spec is QuestMission mission))\r
418                     continue;\r
419                 if (mission.Count(count, mid))\r
420                     NeedSave = true;\r
421             }\r
422         }\r
423 \r
424         public void CountNyukyo() => Increment(503);\r
425 \r
426         public void CountCharge() => Increment(504);\r
427 \r
428         public void InspectCreateItem(string request)\r
429         {\r
430             var values = HttpUtility.ParseQueryString(request);\r
431             var count = values["api_multiple_flag"] == "1" ? 3 : 1;\r
432             Add(605, count);\r
433             Add(607, count);\r
434         }\r
435 \r
436         public void CountCreateShip()\r
437         {\r
438             Increment(606);\r
439             Increment(608);\r
440         }\r
441 \r
442         public void InspectDestroyShip(string request)\r
443         {\r
444             Add(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);\r
445         }\r
446 \r
447         public void CountRemodelSlot() => Increment(619);\r
448 \r
449         public void InspectDestroyItem(string request)\r
450         {\r
451             var values = HttpUtility.ParseQueryString(request);\r
452             var items = values["api_slotitem_ids"].Split(',')\r
453                 .Select(id => _itemInventory[int.Parse(id)].Spec).ToArray();\r
454             Increment(613); // 613: 資源の再利用\r
455             foreach (var quest in _quests.Values)\r
456             {\r
457                 var count = quest.Count;\r
458                 if (count.Spec is QuestDestroyItem destroy)\r
459                 {\r
460                     if (destroy.Count(count, items))\r
461                         NeedSave = true;\r
462                     continue;\r
463                 }\r
464                 if (quest.Id == 680)\r
465                 {\r
466                     count.NowArray[0] += items.Count(spec => spec.Type == 21);\r
467                     count.NowArray[1] += items.Count(spec => spec.Type == 12 || spec.Type == 13);\r
468                     NeedSave = true;\r
469                 }\r
470             }\r
471         }\r
472 \r
473         public void InspectPowerUp(string request, dynamic json)\r
474         {\r
475             if ((int)json.api_powerup_flag == 0)\r
476                 return;\r
477             var values = HttpUtility.ParseQueryString(request);\r
478             foreach (var quest in _quests.Values)\r
479             {\r
480                 var count = quest.Count;\r
481                 if (!(count.Spec is QuestPowerUp))\r
482                     continue;\r
483                 if (quest.Id == 714 || quest.Id == 715)\r
484                 {\r
485                     if (ShipTypeById(values["api_id"]) != 2)\r
486                         return;\r
487                     var ships = values["api_id_items"].Split(',').Select(ShipTypeById).ToArray();\r
488                     var required = quest.Id == 714 ? 2 : quest.Id == 715 ? 3 : -1;\r
489                     if (ships.Count(type => type == required) < 3)\r
490                         return;\r
491                 }\r
492                 if (quest.Id == 716 || quest.Id == 717)\r
493                 {\r
494                     if (!new[] {3, 4, 21}.Contains(ShipTypeById(values["api_id"])))\r
495                         return;\r
496                     var ships = values["api_id_items"].Split(',').Select(ShipTypeById).ToArray();\r
497                     if (quest.Id == 716)\r
498                     {\r
499                         if (ships.Count(type => new[] {3, 4, 21}.Contains(type)) < 3)\r
500                             return;\r
501                     }\r
502                     else\r
503                     {\r
504                         if (ships.Count(type => new[] {5, 6}.Contains(type)) < 3)\r
505                             return;\r
506                     }\r
507                 }\r
508                 Increment(count);\r
509             }\r
510         }\r
511 \r
512         private int ShipTypeById(string id)\r
513         {\r
514             return _shipInventory[int.Parse(id)].Spec.ShipType;\r
515         }\r
516 \r
517         private void Increment(QuestCount count)\r
518         {\r
519             Add(count, 1);\r
520         }\r
521 \r
522         private void Add(QuestCount count, int value)\r
523         {\r
524             count.Now += value;\r
525             NeedSave = true;\r
526         }\r
527 \r
528         private void Increment(int id)\r
529         {\r
530             Add(id, 1);\r
531         }\r
532 \r
533         private void Add(int id, int value)\r
534         {\r
535             if (!_quests.TryGetValue(id, out var quest))\r
536                 return;\r
537             quest.Count.Now += value;\r
538             NeedSave = true;\r
539         }\r
540 \r
541         private void IncrementNth(QuestCount count, int n)\r
542         {\r
543             count.NowArray[n]++;\r
544             NeedSave = true;\r
545         }\r
546     }\r
547 }