OSDN Git Service

cb4583b45f80990b1a5ef4812550e9caf488eac9
[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             if (Spec is QuestSortie spec && spec.Maps != null && spec.MaxArray != null)\r
119             {\r
120                 return string.Join(" ", spec.Maps.Zip(NowArray, (map, n) => $"{MapString(map)}:{n}"));\r
121             }\r
122             return string.Join(" ", (Id switch\r
123             {\r
124                 426 => new[] {"警備任務", "対潜警戒任務", "海上護衛任務", "強硬偵察任務"},\r
125                 428 => new[] {"対潜警戒任務", "海峡警備行動", "長時間対潜警戒"},\r
126                 688 => new[] {"艦戦", "艦爆", "艦攻", "水偵"},\r
127                 _ => new string[0]\r
128             }).Zip(NowArray, (entry, n) => $"{entry}{n}"));\r
129         }\r
130 \r
131         public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ??\r
132                                Spec.Max != 0 && Now >= Spec.Max;\r
133     }\r
134 \r
135     public class QuestCounter\r
136     {\r
137         private readonly QuestInfo _questInfo;\r
138         private readonly ItemInfo _itemInfo;\r
139         private readonly BattleInfo _battleInfo;\r
140         private readonly SortedDictionary<int, QuestStatus> _quests;\r
141         private int _map;\r
142         private bool _boss;\r
143 \r
144         private class ResultShipSpecs\r
145         {\r
146             public ShipSpec[] Specs { get; }\r
147             public NameChecker Names { get; }\r
148             public int[] Types { get; }\r
149             public int[] Classes { get; }\r
150             public ShipSpec Flagship { get; }\r
151             public int FlagshipType { get; }\r
152 \r
153             public class NameChecker\r
154             {\r
155                 private readonly string[] _names;\r
156 \r
157                 public NameChecker(ShipSpec[] specs)\r
158                 {\r
159                     _names = specs.Select(spec => spec.Name).ToArray();\r
160                 }\r
161 \r
162                 public bool Contains(string demand)\r
163                 {\r
164                     return _names.Any(name => name.StartsWith(demand));\r
165                 }\r
166 \r
167                 public int Count(params string[] demands)\r
168                 {\r
169                     return demands.Sum(demand => _names.Count(name => name.StartsWith(demand)));\r
170                 }\r
171             }\r
172 \r
173             public ResultShipSpecs(BattleInfo battleInfo)\r
174             {\r
175                 Specs = battleInfo.Result?.Friend.Main.Where(s => s.NowHp > 0).Select(ship => ship.Spec).ToArray() ??\r
176                         new ShipSpec[0];\r
177                 Names = new NameChecker(Specs);\r
178                 Types = Specs.Select(spec => spec.ShipType).ToArray();\r
179                 Classes = Specs.Select(spec => spec.ShipClass).ToArray();\r
180                 Flagship = Specs.FirstOrDefault();\r
181                 FlagshipType = Types.FirstOrDefault();\r
182             }\r
183         }\r
184 \r
185         public QuestCounter(QuestInfo questInfo, ItemInfo itemInfo, BattleInfo battleInfo)\r
186         {\r
187             _questInfo = questInfo;\r
188             _quests = questInfo.QuestDictionary;\r
189             _itemInfo = itemInfo;\r
190             _battleInfo = battleInfo;\r
191         }\r
192 \r
193         private bool NeedSave\r
194         {\r
195             set => _questInfo.NeedSave = value;\r
196         }\r
197 \r
198         public void InspectMapStart(dynamic json)\r
199         {\r
200             if (_quests.TryGetValue(214, out var ago)) // あ号\r
201                 ago.Count.NowArray[0]++;\r
202             InspectMapNext(json);\r
203         }\r
204 \r
205         public void InspectMapNext(dynamic json)\r
206         {\r
207             _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;\r
208             if (_map == 72)\r
209             {\r
210                 var cell = json.api_no() ? (int)json.api_no : 0;\r
211                 _map *= 10;\r
212                 if (cell == 7)\r
213                     _map++;\r
214                 if (cell == 15)\r
215                     _map += 2;\r
216             }\r
217             _boss = (int)json.api_event_id == 5;\r
218 \r
219             if (_quests.TryGetValue(861, out var q861) && _map == 16 && (int)json.api_event_id == 8)\r
220             {\r
221                 if (new ResultShipSpecs(_battleInfo).Types.Count(s => s == 10 || s == 22) == 2)\r
222                     Increment(q861.Count);\r
223             }\r
224         }\r
225 \r
226         public void InspectBattleResult(dynamic json)\r
227         {\r
228             var rank = json.api_win_rank;\r
229             foreach (var count in _quests.Values.Select(q => q.Count))\r
230             {\r
231                 switch (count.Spec)\r
232                 {\r
233                     case QuestSortie sortie:\r
234                         if (!FleetCheck(count.Id))\r
235                             continue;\r
236                         if (!_boss && count.Id == 216)\r
237                         {\r
238                             Increment(count);\r
239                             continue;\r
240                         }\r
241                         if (sortie.Count(count, rank, _map, _boss))\r
242                             NeedSave = true;\r
243                         continue;\r
244                     case QuestEnemyType enemyType:\r
245                         var num = enemyType.CountResult(\r
246                             _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));\r
247                         if (num > 0)\r
248                             Add(count, num);\r
249                         continue;\r
250                 }\r
251                 if (count.Id == 214)\r
252                     CountAgo(count, rank);\r
253             }\r
254         }\r
255 \r
256         private void CountAgo(QuestCount count, string rank)\r
257         {\r
258             if (QuestSortie.CompareRank(rank, "S") == 0)\r
259                 IncrementNth(count, 1);\r
260             if (!_boss)\r
261                 return;\r
262             IncrementNth(count, 2);\r
263             if (QuestSortie.CompareRank(rank, "B") <= 0)\r
264                 IncrementNth(count, 3);\r
265         }\r
266 \r
267         private bool FleetCheck(int id)\r
268         {\r
269             var specs = new ResultShipSpecs(_battleInfo);\r
270             switch (id)\r
271             {\r
272                 case 249:\r
273                     return specs.Names.Count("妙高", "那智", "羽黒") == 3;\r
274                 case 257:\r
275                     return specs.FlagshipType == 3 && specs.Types.Count(s => s == 3) <= 3 &&\r
276                            specs.Types.All(s => s == 2 || s == 3);\r
277                 case 259:\r
278                     return specs.Types.Count(type => type == 3) > 0 && specs.Classes.Count(c => new[]\r
279                     {\r
280                         2, // 伊勢型\r
281                         19, // 長門型\r
282                         26, // 扶桑型\r
283                         37 // 大和型\r
284                     }.Contains(c)) == 3;\r
285                 case 264:\r
286                     return specs.Types.Count(type => type == 2) >= 2 &&\r
287                            specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2;\r
288                 case 266:\r
289                     return specs.FlagshipType == 2 &&\r
290                            specs.Types.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5});\r
291                 case 280:\r
292                 case 284:\r
293                     return specs.Types.Count(type => type == 1 || type == 2) >= 3 &&\r
294                            specs.Types.Intersect(new[] {3, 4, 7, 21}).Any();\r
295                 case 862:\r
296                     return specs.Types.Count(s => s == 3) >= 2 && specs.Types.Count(s => s == 16) >= 1;\r
297                 case 873:\r
298                     return specs.Types.Count(type => type == 3) >= 1;\r
299                 case 875:\r
300                     return specs.Names.Contains("長波改二") &&\r
301                            specs.Names.Count("朝霜改", "高波改", "沖波改") > 0;\r
302                 case 888:\r
303                     return specs.Names.Count("鳥海", "青葉", "衣笠", "加古", "天竜", "夕張") >= 4;\r
304                 case 894:\r
305                     return specs.Specs.Any(spec => spec.IsAircraftCarrier);\r
306                 case 318:\r
307                     return specs.Types.Count(type => type == 3) >= 2;\r
308                 case 330:\r
309                     return specs.Flagship.IsAircraftCarrier &&\r
310                            specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2 &&\r
311                            specs.Types.Count(type => type == 2) >= 2;\r
312                 case 337:\r
313                     return specs.Names.Count("陽炎", "不知火", "霰", "霞") == 4;\r
314                 default:\r
315                     return true;\r
316             }\r
317         }\r
318 \r
319         private int _questFleet;\r
320 \r
321         public void StartPractice(string request)\r
322         {\r
323             var values = HttpUtility.ParseQueryString(request);\r
324             _questFleet = int.Parse(values["api_deck_id"]) - 1;\r
325         }\r
326 \r
327         public void InspectPracticeResult(dynamic json)\r
328         {\r
329             foreach (var count in _quests.Values.Select(q => q.Count))\r
330             {\r
331                 if (!FleetCheck(count.Id))\r
332                     continue;\r
333                 if (count.Id == 318 && _questFleet != 0)\r
334                     continue;\r
335                 if (!(count.Spec is QuestPractice practice))\r
336                     continue;\r
337                 if (practice.Check(json.api_win_rank))\r
338                     Increment(count);\r
339             }\r
340         }\r
341 \r
342         private readonly int[] _missionId = new int[ShipInfo.FleetCount];\r
343 \r
344         public void InspectDeck(dynamic json)\r
345         {\r
346             foreach (var entry in json)\r
347                 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];\r
348         }\r
349 \r
350         public void InspectMissionResult(string request, dynamic json)\r
351         {\r
352             var values = HttpUtility.ParseQueryString(request);\r
353             var deck = int.Parse(values["api_deck_id"]);\r
354             if ((int)json.api_clear_result == 0)\r
355                 return;\r
356             var mid = _missionId[deck - 1];\r
357             foreach (var count in _quests.Values.Select(q => q.Count))\r
358             {\r
359                 if (!(count.Spec is QuestMission mission))\r
360                     continue;\r
361                 if (mission.Count(count, mid))\r
362                     NeedSave = true;\r
363             }\r
364         }\r
365 \r
366         public void CountNyukyo() => Increment(503);\r
367 \r
368         public void CountCharge() => Increment(504);\r
369 \r
370         public void InspectCreateItem(string request)\r
371         {\r
372             var values = HttpUtility.ParseQueryString(request);\r
373             var count = values["api_multiple_flag"] == "1" ? 3 : 1;\r
374             Add(605, count);\r
375             Add(607, count);\r
376         }\r
377 \r
378         public void CountCreateShip()\r
379         {\r
380             Increment(606);\r
381             Increment(608);\r
382         }\r
383 \r
384         public void InspectDestroyShip(string request)\r
385         {\r
386             Add(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);\r
387         }\r
388 \r
389         public void CountRemodelSlot() => Increment(619);\r
390 \r
391         public void InspectDestroyItem(string request, dynamic json)\r
392         {\r
393             var values = HttpUtility.ParseQueryString(request);\r
394             var items = values["api_slotitem_ids"].Split(',')\r
395                 .Select(id => _itemInfo.GetStatus(int.Parse(id)).Spec).ToArray();\r
396             Increment(613); // 613: 資源の再利用\r
397             foreach (var quest in _quests.Values)\r
398             {\r
399                 var count = quest.Count;\r
400                 if (count.Spec is QuestDestroyItem destroy)\r
401                 {\r
402                     if (destroy.Count(count, items))\r
403                         NeedSave = true;\r
404                     continue;\r
405                 }\r
406                 if (quest.Id == 680)\r
407                 {\r
408                     count.NowArray[0] += items.Count(spec => spec.Type == 21);\r
409                     count.NowArray[1] += items.Count(spec => spec.Type == 12 || spec.Type == 13);\r
410                     NeedSave = true;\r
411                 }\r
412             }\r
413         }\r
414 \r
415         public void InspectPowerUp(dynamic json)\r
416         {\r
417             if ((int)json.api_powerup_flag == 0)\r
418                 return;\r
419             foreach (var quest in _quests.Values)\r
420             {\r
421                 var count = quest.Count;\r
422                 if (!(count.Spec is QuestPowerUp))\r
423                     continue;\r
424                 Increment(count);\r
425             }\r
426         }\r
427 \r
428         public void Increment(QuestCount count)\r
429         {\r
430             Add(count, 1);\r
431         }\r
432 \r
433         public void Add(QuestCount count, int value)\r
434         {\r
435             count.Now += value;\r
436             NeedSave = true;\r
437         }\r
438 \r
439         public void Increment(int id)\r
440         {\r
441             Add(id, 1);\r
442         }\r
443 \r
444         public void Add(int id, int value)\r
445         {\r
446             if (!_quests.TryGetValue(id, out var quest))\r
447                 return;\r
448             quest.Count.Now += value;\r
449             NeedSave = true;\r
450         }\r
451 \r
452         public void IncrementNth(QuestCount count, int n)\r
453         {\r
454             count.NowArray[n]++;\r
455             NeedSave = true;\r
456         }\r
457     }\r
458 }