1 // Copyright (C) 2019 Kazuhiro Fujieda <fujieda@users.osdn.me>
\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
7 // http://www.apache.org/licenses/LICENSE-2.0
\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
16 using System.Collections.Generic;
\r
18 using System.Xml.Serialization;
\r
19 using KancolleSniffer.Util;
\r
21 namespace KancolleSniffer.Model
\r
23 public class QuestCount
\r
25 public int Id { get; set; }
\r
26 public int Now { get; set; }
\r
27 public int[] NowArray { get; set; }
\r
30 public QuestSpec Spec { get; set; }
\r
32 public bool AdjustCount(int progress)
\r
34 if (!Spec.AdjustCount)
\r
36 if (NowArray != null)
\r
38 if (progress != 100)
\r
40 NowArray = NowArray.Zip(Spec.MaxArray, Math.Max).ToArray();
\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
64 var high = (int)Math.Ceiling(max * next / 100.0);
\r
67 Now = low - Spec.Shift;
\r
72 Now = high - 1 - Spec.Shift;
\r
78 public override string ToString()
\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
87 public QuestCount Clone()
\r
89 var clone = (QuestCount)MemberwiseClone();
\r
90 if (NowArray != null)
\r
91 clone.NowArray = (int[])NowArray.Clone();
\r
95 public bool Equals(QuestCount other)
\r
99 if (NowArray == null)
\r
100 return Now == other.Now;
\r
101 return NowArray.SequenceEqual(other.NowArray);
\r
104 private static string MapString(int map)
\r
110 _ => $"{map / 10}-{map % 10}"
\r
114 public string ToToolTip()
\r
116 if (NowArray == null)
\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
125 return string.Join(" ", (Id switch
\r
127 688 => new[] {"艦戦", "艦爆", "艦攻", "水偵"},
\r
129 }).Zip(NowArray, (entry, n) => $"{entry}{n}"));
\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
137 public class QuestCounter
\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
145 private bool _boss;
\r
147 private class ResultShipSpecs
\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
156 public class NameChecker
\r
158 private readonly string[] _names;
\r
160 public NameChecker(ShipSpec[] specs)
\r
162 _names = specs.Select(spec => spec.Name).ToArray();
\r
165 public bool Contains(string demand)
\r
167 return _names.Any(name => name.StartsWith(demand));
\r
170 public int Count(params string[] demands)
\r
172 return demands.Sum(demand => _names.Count(name => name.StartsWith(demand)));
\r
176 public ResultShipSpecs(BattleInfo battleInfo)
\r
178 Specs = battleInfo.Result?.Friend.Main.Where(s => s.NowHp > 0).Select(ship => ship.Spec).ToArray() ??
\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
188 public QuestCounter(QuestInfo questInfo, ItemInventory itemInventory, ShipInventory shipInventory, BattleInfo battleInfo)
\r
190 _questInfo = questInfo;
\r
191 _quests = questInfo.QuestDictionary;
\r
192 _itemInventory = itemInventory;
\r
193 _shipInventory = shipInventory;
\r
194 _battleInfo = battleInfo;
\r
197 private bool NeedSave
\r
199 set => _questInfo.NeedSave = value;
\r
202 public void InspectMapStart(dynamic json)
\r
204 if (_quests.TryGetValue(214, out var ago)) // あ号
\r
205 ago.Count.NowArray[0]++;
\r
206 InspectMapNext(json);
\r
209 public void InspectMapNext(dynamic json)
\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
217 else if (cell == 15)
\r
224 else if (cell == 18)
\r
227 _boss = (int)json.api_event_id == 5;
\r
229 if (_map != 16 || (int)json.api_event_id != 8)
\r
231 foreach (var count in _quests.Values.Select(q => q.Count))
\r
233 if (!(count.Spec is QuestSortie sortie) || sortie.Maps == null)
\r
235 if (!FleetCheck(count.Id))
\r
237 if (sortie.Count(count, "S", _map, true))
\r
242 public void InspectBattleResult(dynamic json)
\r
244 var rank = json.api_win_rank;
\r
245 foreach (var count in _quests.Values.Select(q => q.Count))
\r
247 switch (count.Spec)
\r
249 case QuestSortie sortie:
\r
250 if (!FleetCheck(count.Id))
\r
252 if (!_boss && count.Id == 216)
\r
257 if (sortie.Count(count, rank, _map, _boss))
\r
260 case QuestEnemyType enemyType:
\r
261 var num = enemyType.CountResult(
\r
262 _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));
\r
267 if (count.Id == 214)
\r
268 CountAgo(count, rank);
\r
272 private void CountAgo(QuestCount count, string rank)
\r
274 if (QuestSortie.CompareRank(rank, "S") == 0)
\r
275 IncrementNth(count, 1);
\r
278 IncrementNth(count, 2);
\r
279 if (QuestSortie.CompareRank(rank, "B") <= 0)
\r
280 IncrementNth(count, 3);
\r
283 private bool FleetCheck(int id)
\r
285 var specs = new ResultShipSpecs(_battleInfo);
\r
289 return specs.Names.Count("妙高", "那智", "羽黒") == 3;
\r
291 return specs.FlagshipType == 3 && specs.Types.Count(s => s == 3) <= 3 &&
\r
292 specs.Types.All(s => s == 2 || s == 3);
\r
294 return specs.Types.Count(type => type == 3) > 0 && specs.Classes.Count(c => new[]
\r
300 }.Contains(c)) == 3;
\r
302 return specs.Types.Count(type => type == 2) >= 2 &&
\r
303 specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2;
\r
305 return specs.FlagshipType == 2 &&
\r
306 specs.Types.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5});
\r
309 return specs.Types.Count(type => type == 1 || type == 2) >= 3 &&
\r
310 specs.Types.Intersect(new[] {3, 4, 7, 21}).Any();
\r
312 return new[] {3, 4, 7, 21}.Contains(specs.FlagshipType) &&
\r
313 specs.Types.Count(type => type == 2 || type == 1) >= 3;
\r
315 return specs.Types.Count(s => s == 10 || s == 22) == 2;
\r
317 return specs.Types.Count(s => s == 3) >= 2 && specs.Types.Count(s => s == 16) >= 1;
\r
319 return specs.Types.Count(type => type == 3) >= 1;
\r
321 return specs.Names.Contains("長波改二") &&
\r
322 specs.Names.Count("朝霜改", "高波改", "沖波改") > 0;
\r
324 return specs.Names.Count("鳥海", "青葉", "衣笠", "加古", "古鷹", "天龍", "夕張") >= 4;
\r
326 return specs.Specs.Any(spec => spec.IsAircraftCarrier);
\r
328 return specs.Flagship.Name.StartsWith("夕張改二") &&
\r
329 (specs.Names.Count("睦月", "如月", "弥生", "卯月", "菊月", "望月") >= 2 || specs.Names.Contains("由良改二"));
\r
331 return specs.Names.Count("綾波改二", "敷波改二") == 2;
\r
333 return specs.Types.Count(type => type == 1) >= 3 && specs.Types.Length <= 5;
\r
335 return specs.Flagship.Name.StartsWith("明石") && specs.Types.Count(type => type == 2) >= 3;
\r
337 return specs.Types.Count(type => type == 5) >= 3 && specs.Types.Count(type => type == 2) >= 1;
\r
339 return specs.Names.Count("羽黒", "足柄", "妙高", "高雄", "神風") >= 2;
\r
341 return specs.Types.Count(type => type == 3) >= 2;
\r
343 return specs.Types.Count(type => type == 2 || type == 1) >= 2;
\r
345 return specs.Flagship.IsAircraftCarrier &&
\r
346 specs.Specs.Count(spec => spec.IsAircraftCarrier) >= 2 &&
\r
347 specs.Types.Count(type => type == 2) >= 2;
\r
349 return specs.Names.Count("陽炎", "不知火", "霰", "霞") == 4;
\r
351 return specs.Names.Count("磯波", "浦波", "綾波", "敷波") == 4;
\r
353 var t12 = specs.Types.Count(type => type == 1 || type == 2);
\r
354 return t12 >= 4 || t12 >= 3 && specs.Types.Intersect(new[] {3, 4, 7, 21}).Any();
\r
356 return specs.Names.Count("Warspite", "金剛", "Ark Royal", "Nelson", "Jervis", "Janus") >= 4;
\r
358 return specs.Names.Count("夕雲改二", "巻雲改二", "風雲改二", "秋雲改二") == 4;
\r
364 private int _questFleet;
\r
366 public void StartPractice(string request)
\r
368 var values = HttpUtility.ParseQueryString(request);
\r
369 _questFleet = int.Parse(values["api_deck_id"]) - 1;
\r
372 public void InspectPracticeResult(dynamic json)
\r
374 foreach (var count in _quests.Values.Select(q => q.Count))
\r
376 if (!FleetCheck(count.Id))
\r
378 if (count.Id == 318 && _questFleet != 0)
\r
380 if (!(count.Spec is QuestPractice practice))
\r
382 if (practice.Check(json.api_win_rank))
\r
387 private readonly int[] _missionId = new int[ShipInfo.FleetCount];
\r
389 public void InspectDeck(dynamic json)
\r
391 foreach (var entry in json)
\r
392 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];
\r
395 public void InspectMissionResult(string request, dynamic json)
\r
397 var values = HttpUtility.ParseQueryString(request);
\r
398 var deck = int.Parse(values["api_deck_id"]);
\r
399 if ((int)json.api_clear_result == 0)
\r
401 var mid = _missionId[deck - 1];
\r
402 foreach (var count in _quests.Values.Select(q => q.Count))
\r
404 if (!(count.Spec is QuestMission mission))
\r
406 if (mission.Count(count, mid))
\r
411 public void CountNyukyo() => Increment(503);
\r
413 public void CountCharge() => Increment(504);
\r
415 public void InspectCreateItem(string request)
\r
417 var values = HttpUtility.ParseQueryString(request);
\r
418 var count = values["api_multiple_flag"] == "1" ? 3 : 1;
\r
423 public void CountCreateShip()
\r
429 public void InspectDestroyShip(string request)
\r
431 Add(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);
\r
434 public void CountRemodelSlot() => Increment(619);
\r
436 public void InspectDestroyItem(string request)
\r
438 var values = HttpUtility.ParseQueryString(request);
\r
439 var items = values["api_slotitem_ids"].Split(',')
\r
440 .Select(id => _itemInventory[int.Parse(id)].Spec).ToArray();
\r
441 Increment(613); // 613: 資源の再利用
\r
442 foreach (var quest in _quests.Values)
\r
444 var count = quest.Count;
\r
445 if (count.Spec is QuestDestroyItem destroy)
\r
447 if (destroy.Count(count, items))
\r
451 if (quest.Id == 680)
\r
453 count.NowArray[0] += items.Count(spec => spec.Type == 21);
\r
454 count.NowArray[1] += items.Count(spec => spec.Type == 12 || spec.Type == 13);
\r
460 public void InspectPowerUp(string request, dynamic json)
\r
462 if ((int)json.api_powerup_flag == 0)
\r
464 foreach (var quest in _quests.Values)
\r
466 var count = quest.Count;
\r
467 if (!(count.Spec is QuestPowerUp))
\r
469 if (quest.Id == 714 || quest.Id == 715)
\r
471 var values = HttpUtility.ParseQueryString(request);
\r
472 if (_shipInventory[int.Parse(values["api_id"])].Spec.ShipType != 2)
\r
474 var ships = values["api_id_items"].Split(',').Select(id => _shipInventory[int.Parse(id)]).ToArray();
\r
475 var type = quest.Id == 714 ? 2 : quest.Id == 715 ? 3 : -1;
\r
476 if (ships.Count(s => s.Spec.ShipType == type) < 3)
\r
483 private void Increment(QuestCount count)
\r
488 private void Add(QuestCount count, int value)
\r
490 count.Now += value;
\r
494 private void Increment(int id)
\r
499 private void Add(int id, int value)
\r
501 if (!_quests.TryGetValue(id, out var quest))
\r
503 quest.Count.Now += value;
\r
507 private void IncrementNth(QuestCount count, int n)
\r
509 count.NowArray[n]++;
\r