1 // Copyright (C) 2013, 2015 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
17 using System.Drawing;
\r
19 using System.Windows.Forms;
\r
20 using System.Xml.Serialization;
\r
21 using KancolleSniffer.Util;
\r
22 using static System.Math;
\r
24 namespace KancolleSniffer.Model
\r
26 public class QuestStatus
\r
28 public int Id { get; set; }
\r
29 public int Category { get; set; }
\r
30 public string Name { get; set; }
\r
31 public string Detail { get; set; }
\r
32 public int[] Material { get; set; }
\r
33 public int Progress { get; set; }
\r
36 public QuestCount Count { get; set; }
\r
39 public Color Color { get; set; }
\r
41 public string ToToolTip() =>
\r
43 (Material == null || Material.All(x => x == 0)
\r
45 : "\r\n" + string.Join(" ",
\r
46 new[] {"燃", "弾", "鋼", "ボ", "建造", "修復", "開発", "改修"}
\r
47 .Zip(Material, (m, num) => num == 0 ? "" : m + num)
\r
48 .Where(s => !string.IsNullOrEmpty(s))));
\r
51 public enum QuestInterval
\r
53 // ReSharper disable once UnusedMember.Global
\r
61 public class QuestSpec
\r
63 public QuestInterval Interval { get; set; }
\r
64 public int Max { get; set; }
\r
65 public int[] MaxArray { get; set; }
\r
66 public bool AdjustCount { get; set; } = true;
\r
67 public int Shift { get; set; }
\r
68 public int[] Material { get; set; }
\r
71 public class QuestSortie : QuestSpec
\r
73 public string Rank { get; set; }
\r
74 public int[] Maps { get; set; }
\r
76 public static int CompareRank(string a, string b)
\r
78 const string ranks = "SABCDE";
\r
79 return ranks.IndexOf(a, StringComparison.Ordinal) -
\r
80 ranks.IndexOf(b, StringComparison.Ordinal);
\r
83 public bool Check(string rank, int map, bool boss)
\r
85 return (Rank == null || CompareRank(rank, Rank) <= 0) &&
\r
86 (Maps == null || Maps.Contains(map) && boss);
\r
90 public class QuestEnemyType : QuestSpec
\r
92 public int[] EnemyType { get; set; } = new int[0];
\r
94 public int CountResult(IEnumerable<ShipStatus> enemyResult) =>
\r
95 enemyResult.Count(ship => ship.NowHp == 0 && EnemyType.Contains(ship.Spec.ShipType));
\r
98 public class QuestPractice : QuestSpec
\r
100 public bool Win { get; set; }
\r
101 public bool Check(string rank) => !Win || QuestSortie.CompareRank(rank, "B") <= 0;
\r
104 public class QuestMission : QuestSpec
\r
106 public int[] Ids { get; set; }
\r
107 public bool Check(int id) => Ids == null || Ids.Contains(id);
\r
110 public class QuestDestroyItem : QuestSpec
\r
112 public int[] Types { get; set; }
\r
113 public int[] Ids { get; set; }
\r
115 public bool Count(QuestCount count, ItemSpec[] specs)
\r
117 if (count.NowArray == null)
\r
119 var num = specs.Count(spec => Types?.Contains(spec.Type) ?? (Ids?.Contains(spec.Id) ?? true));
\r
123 if (Types == null && Ids == null)
\r
125 var result = false;
\r
126 for (var i = 0; i < count.NowArray.Length; i++)
\r
128 var num = specs.Count(spec => Types != null ? Types[i] == spec.Type : Ids[i] == spec.Id);
\r
129 count.NowArray[i] += num;
\r
137 public class QuestPowerUp : QuestSpec
\r
141 public class QuestCount
\r
143 public int Id { get; set; }
\r
144 public int Now { get; set; }
\r
145 public int[] NowArray { get; set; }
\r
148 public QuestSpec Spec { get; set; }
\r
150 public bool AdjustCount(int progress)
\r
152 if (!Spec.AdjustCount)
\r
154 if (NowArray != null)
\r
156 if (progress != 100)
\r
158 NowArray = NowArray.Zip(Spec.MaxArray, Max).ToArray();
\r
177 var now = Now + Spec.Shift;
\r
178 var max = Spec.Max + Spec.Shift;
\r
179 var low = (int)Ceiling(max * progress / 100.0);
\r
180 if (low >= max && progress != 100)
\r
182 var high = (int)Ceiling(max * next / 100.0);
\r
185 Now = low - Spec.Shift;
\r
190 Now = high - 1 - Spec.Shift;
\r
196 public override string ToString()
\r
198 if (Id == 426 || Id == 854 || Id == 873 || Id == 888)
\r
199 return $"{NowArray.Count(n => n >= 1)}/{Spec.MaxArray.Length}";
\r
200 return NowArray != null
\r
201 ? string.Join(" ", NowArray.Zip(Spec.MaxArray, (n, m) => $"{n}/{m}"))
\r
202 : $"{Now}/{Spec.Max}";
\r
205 public string ToToolTip()
\r
210 return string.Join(" ",
\r
211 new[] {"警備任務", "対潜警戒任務", "海上護衛任務", "強硬偵察任務"}
\r
212 .Zip(NowArray, (mission, n) => n >= 1 ? mission : "")
\r
213 .Where(s => !string.IsNullOrEmpty(s)));
\r
215 return string.Join(" ",
\r
216 new[] {"対潜警戒任務", "海峡警備行動", "長時間対潜警戒"}.Zip(NowArray, (mission, n) => n >= 1 ? mission + n : "")
\r
217 .Where(s => !string.IsNullOrEmpty(s)));
\r
219 return string.Join(" ",
\r
220 new[] {"2-4", "6-1", "6-3", "6-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")
\r
221 .Where(s => !string.IsNullOrEmpty(s)));
\r
223 return string.Join(" ",
\r
224 new[] {"3-1", "3-2", "3-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")
\r
225 .Where(s => !string.IsNullOrEmpty(s)));
\r
227 return string.Join(" ",
\r
228 new[] {"5-1", "5-3", "5-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")
\r
229 .Where(s => !string.IsNullOrEmpty(s)));
\r
231 return string.Join(" ",
\r
232 new[] {"艦戦", "艦爆", "艦攻", "水偵"}.Zip(NowArray, (type, n) => n >= 1 ? type + n : "")
\r
233 .Where(s => !string.IsNullOrEmpty(s)));
\r
238 public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ?? Spec.Max != 0 && Now >= Spec.Max;
\r
242 public class QuestCountList
\r
244 private const QuestInterval Daily = QuestInterval.Daily;
\r
245 private const QuestInterval Weekly = QuestInterval.Weekly;
\r
246 private const QuestInterval Monthly = QuestInterval.Monthly;
\r
247 private const QuestInterval Quarterly = QuestInterval.Quarterly;
\r
250 /// このテーブルは七四式電子観測儀を参考に作成した。
\r
251 /// https://github.com/andanteyk/ElectronicObserver/blob/develop/ElectronicObserver/Data/Quest/QuestProgressManager.cs
\r
253 private static readonly Dictionary<int, QuestSpec> QuestSpecs = new Dictionary<int, QuestSpec>
\r
255 {201, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 0, 1, 0}}}, // 201: 敵艦隊を撃滅せよ!
\r
256 {216, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 1, 1, 0}}}, // 216: 敵艦隊主力を撃滅せよ!
\r
257 {210, new QuestSortie {Interval = Daily, Max = 10, Material = new[] {0, 0, 1, 0}}}, // 210: 敵艦隊を10回邀撃せよ!
\r
258 {211, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {7, 11}, Material = new[] {0, 2, 0, 0}}}, // 211: 敵空母を3隻撃沈せよ!
\r
259 {212, new QuestEnemyType {Interval = Daily, Max = 5, EnemyType = new[] {15}, Material = new[] {0, 0, 2, 0}}}, // 212: 敵輸送船団を叩け!
\r
260 {218, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {15}, Material = new[] {0, 1, 1, 0}}}, // 218: 敵補給艦を3隻撃沈せよ!
\r
261 {226, new QuestSortie {Interval = Daily, Max = 5, Rank = "B", Maps = new[] {21, 22, 23, 24, 25}, Material = new[] {1, 1, 0, 0}}}, // 226: 南西諸島海域の制海権を握れ!
\r
262 {230, new QuestEnemyType {Interval = Daily, Max = 6, EnemyType = new[] {13}, Material = new[] {0, 1, 0, 0}}}, // 230: 敵潜水艦を制圧せよ!
\r
264 {213, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {15}, Material = new[] {0, 0, 3, 0}}}, // 213: 海上通商破壊作戦
\r
265 {214, new QuestSpec {Interval = Weekly, MaxArray = new[] {36, 6, 24, 12}, Material = new[] {2, 0, 2, 0}}}, // 214: あ号作戦
\r
266 {220, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {7, 11}, Material = new[] {0, 0, 2, 0}}}, // 220: い号作戦
\r
267 {221, new QuestEnemyType {Interval = Weekly, Max = 50, EnemyType = new[] {15}, Material = new[] {0, 3, 0, 0}}}, // 221: ろ号作戦
\r
268 {228, new QuestEnemyType {Interval = Weekly, Max = 15, EnemyType = new[] {13}, Material = new[] {0, 2, 0, 1}}}, // 228: 海上護衛戦
\r
269 {229, new QuestSortie {Interval = Weekly, Max = 12, Rank = "B", Maps = new[] {41, 42, 43, 44, 45}, Material = new[] {0, 0, 2, 0}}}, // 229: 敵東方艦隊を撃滅せよ!
\r
270 {241, new QuestSortie {Interval = Weekly, Max = 5, Rank = "B", Maps = new[] {33, 34, 35}, Material = new[] {0, 0, 3, 3}}}, // 241: 敵北方艦隊主力を撃滅せよ!
\r
271 {242, new QuestSortie {Interval = Weekly, Max = 1, Rank = "B", Maps = new[] {44}, Material = new[] {0, 1, 1, 0}}}, // 242: 敵東方中枢艦隊を撃破せよ!
\r
272 {243, new QuestSortie {Interval = Weekly, Max = 2, Rank = "S", Maps = new[] {52}, Material = new[] {0, 0, 2, 2}}}, // 243: 南方海域珊瑚諸島沖の制空権を握れ!
\r
273 {249, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 5, 0}}}, // 249: 「第五戦隊」出撃せよ!
\r
274 {256, new QuestSortie {Interval = Monthly, Max = 3, Rank = "S", Maps = new[] {61}, Material = new[] {0, 0, 0, 0}}}, // 256: 「潜水艦隊」出撃せよ!
\r
275 {257, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 0, 3}}}, // 257: 「水雷戦隊」南西へ!
\r
276 {259, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 3, 0, 4}}}, // 259: 「水上打撃部隊」南方へ!
\r
277 {261, new QuestSortie {Interval = Weekly, Max = 3, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 0, 3}}}, // 261: 海上輸送路の安全確保に努めよ!
\r
278 {264, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 0, 2}}}, // 264: 「空母機動部隊」西へ!
\r
279 {265, new QuestSortie {Interval = Monthly, Max = 10, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 5, 3}}}, // 265: 海上護衛強化月間
\r
280 {266, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 4, 2}}}, // 266: 「水上反撃部隊」突入せよ!
\r
282 {822, new QuestSortie {Interval = Quarterly, Max = 2, Rank = "S", Maps = new[] {24}, Material = new[] {0, 0, 0, 5}}}, // 822: 沖ノ島海域迎撃戦
\r
283 {854, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 0, 4}}}, // 854: 戦果拡張任務!「Z作戦」前段作戦
\r
284 {861, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 4, 0, 0}}}, // 861: 強行輸送艦隊、抜錨!
\r
285 {862, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 8, 4}}}, // 862: 前線の航空偵察を実施せよ!
\r
286 {873, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1}, Material = new[] {0, 0, 0, 0}}}, // 873: 北方海域警備を実施せよ!
\r
287 {875, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 0, 0}}}, // 875: 精鋭「三一駆」、鉄底海域に突入せよ!
\r
288 {888, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1}, Material = new[] {0, 0, 0, 0}}}, // 888: 新編成「三川艦隊」、鉄底海峡に突入せよ!
\r
290 {303, new QuestPractice {Interval = Daily, Max = 3, Win = false, Material = new[] {1, 0, 0, 0}}}, // 303: 「演習」で練度向上!
\r
291 {304, new QuestPractice {Interval = Daily, Max = 5, Win = true, Material = new[] {0, 0, 1, 0}}}, // 304: 「演習」で他提督を圧倒せよ!
\r
292 {302, new QuestPractice {Interval = Weekly, Max = 20, Win = true, Material = new[] {0, 0, 2, 1}}}, // 302: 大規模演習
\r
293 {311, new QuestPractice {Interval = Daily, Max = 7, Win = true, Material = new[] {0, 2, 0, 0}}}, // 311: 精鋭艦隊演習
\r
294 {315, new QuestPractice {Interval = Daily, Max = 8, Win = true, Material = new[] {0, 0, 0, 0}}}, // 315: 春季大演習
\r
295 {318, new QuestSpec {Interval = Daily, Max = 3, Material = new[] {0, 2, 2, 0}, AdjustCount = false}}, // 318: 給糧艦「伊良湖」の支援
\r
297 {402, new QuestMission {Interval = Daily, Max = 3, Material = new[] {0, 0, 1, 0}}}, // 402: 「遠征」を3回成功させよう!
\r
298 {403, new QuestMission {Interval = Daily, Max = 10, Material = new[] {0, 0, 0, 0}}}, // 403: 「遠征」を10回成功させよう!
\r
299 {404, new QuestMission {Interval = Weekly, Max = 30, Material = new[] {0, 0, 3, 0}}}, // 404: 大規模遠征作戦、発令!
\r
300 {410, new QuestMission {Interval = Weekly, Max = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 0, 0}}}, // 410: 南方への輸送作戦を成功させよ!
\r
301 {411, new QuestMission {Interval = Weekly, Max = 6, Shift = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 2, 1}}}, // 411: 南方への鼠輸送を継続実施せよ!
\r
302 {424, new QuestMission {Interval = Monthly, Max = 4, Shift = 1, Ids = new[] {5}, Material = new[] {0, 0, 0, 0}}}, // 424: 輸送船団護衛を強化せよ!
\r
303 {426, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 4, 0}}}, // 426: 海上通商航路の警戒を厳とせよ!
\r
304 {428, new QuestSpec {Interval = Quarterly, MaxArray = new[] {2, 2, 2}, Material = new[] {0, 0, 0, 3}}}, // 428: 近海に侵入する敵潜を制圧せよ!
\r
306 {503, new QuestSpec {Interval = Daily, Max = 5, Material = new[] {0, 2, 0, 0}}}, // 503: 艦隊大整備!
\r
307 {504, new QuestSpec {Interval = Daily, Max = 15, Material = new[] {1, 0, 1, 0}}}, // 504: 艦隊酒保祭り!
\r
309 {605, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {1, 0, 1, 0}}}, // 605: 新装備「開発」指令
\r
310 {606, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 1, 1, 0}}}, // 606: 新造艦「建造」指令
\r
311 {607, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {0, 0, 2, 0}}}, // 607: 装備「開発」集中強化!
\r
312 {608, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {1, 0, 2, 0}}}, // 608: 艦娘「建造」艦隊強化!
\r
313 {609, new QuestSpec {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 609: 軍縮条約対応!
\r
314 {619, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 0, 0, 1}}}, // 619: 装備の改修強化
\r
316 {613, new QuestSpec {Interval = Weekly, Max = 24, Material = new[] {0, 0, 0, 0}}}, // 613: 資源の再利用
\r
317 {638, new QuestDestroyItem {Interval = Weekly, Max = 6, Types = new[] {21}, Material = new[] {0, 0, 2, 1}}}, // 638: 対空機銃量産
\r
318 {643, new QuestDestroyItem {Interval = Quarterly, Max = 2, Ids = new[] {20}, Material = new[] {0, 0, 2, 0}, AdjustCount = false}}, // 643: 主力「陸攻」の調達
\r
319 {645, new QuestDestroyItem {Interval = Monthly, Max = 1, Ids = new[] {35}, Material = new[] {0, 0, 0, 0}, AdjustCount = false}}, // 645: 「洋上補給」物資の調達
\r
320 {663, new QuestDestroyItem {Interval = Quarterly, Max = 10, Types = new[] {3}, Material = new[] {0, 0, 3, 0}}}, // 663: 新型艤装の継続研究
\r
321 {673, new QuestDestroyItem {Interval = Daily, Max = 4, Types = new[] {1}, Shift = 1, Material = new[] {0, 0, 1, 0}}}, // 673: 装備開発力の整備
\r
322 {674, new QuestDestroyItem {Interval = Daily, Max = 3, Types = new[] {21}, Shift = 2, Material = new[] {0, 1, 1, 0}}}, // 674: 工廠環境の整備
\r
323 {675, new QuestDestroyItem {Interval = Quarterly, MaxArray = new[] {6, 4}, Types = new[] {6, 21}, Material = new[] {0, 0, 0, 0}}}, // 675: 運用装備の統合整備
\r
324 {676, new QuestDestroyItem {Interval = Weekly, MaxArray = new[] {3, 3, 1}, Types = new[] {2, 4, 30}, Material = new[] {0, 1, 7, 0}}}, // 676: 装備開発力の集中整備
\r
325 {677, new QuestDestroyItem {Interval = Weekly, MaxArray = new[] {4, 2, 3}, Types = new[] {3, 10, 5}, Material = new[] {0, 5, 0, 0}}}, // 677: 継戦支援能力の整備
\r
326 {678, new QuestDestroyItem {Interval = Quarterly, MaxArray = new[] {3, 5}, Ids = new[] {19, 20}, Material = new[] {0, 0, 8, 0}}}, // 678: 主力艦上戦闘機の更新
\r
327 {680, new QuestSpec {Interval = Quarterly, MaxArray = new[] {4, 4}, Material = new[] {0, 0, 6, 0}}}, // 680: 対空兵装の整備拡充
\r
328 {688, new QuestDestroyItem {Interval = Quarterly, MaxArray = new[] {3, 3, 3, 3}, Types = new[] {6, 7, 8, 10}, Material = new[] {0, 0, 0, 0}}}, // 688: 航空戦力の強化
\r
330 {702, new QuestPowerUp {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 702: 艦の「近代化改修」を実施せよ!
\r
331 {703, new QuestPowerUp {Interval = Weekly, Max = 15, Material = new[] {1, 0, 2, 0}}} // 703: 「近代化改修」を進め、戦備を整えよ!
\r
335 private readonly Dictionary<int, QuestCount> _countDict = new Dictionary<int, QuestCount>();
\r
337 public QuestCount GetCount(int id)
\r
339 if (_countDict.TryGetValue(id, out var value))
\r
341 if (QuestSpecs.TryGetValue(id, out var spec))
\r
343 var nowArray = spec.MaxArray?.Select(x => 0).ToArray();
\r
344 return _countDict[id] = new QuestCount
\r
348 NowArray = nowArray,
\r
352 return new QuestCount {Spec = new QuestSpec {Material = new int[0], AdjustCount = false}};
\r
355 public void Remove(int id)
\r
357 _countDict.Remove(id);
\r
360 public void Remove(QuestInterval interval)
\r
363 _countDict.Where(pair => pair.Value.Spec.Interval == interval).Select(pair => pair.Key).ToArray())
\r
365 _countDict.Remove(id);
\r
369 public IEnumerable<QuestCount> CountList
\r
371 get => _countDict.Values.Where(c => c.Now > 0 || (c.NowArray?.Any(n => n > 0) ?? false));
\r
376 foreach (var count in value)
\r
378 count.Spec = QuestSpecs[count.Id];
\r
379 _countDict[count.Id] = count;
\r
385 public class QuestInfo : IHaveState
\r
387 private readonly SortedDictionary<int, QuestStatus> _quests = new SortedDictionary<int, QuestStatus>();
\r
388 private readonly QuestCountList _countList = new QuestCountList();
\r
389 private readonly ItemInfo _itemInfo;
\r
390 private readonly BattleInfo _battleInfo;
\r
391 private readonly Func<DateTime> _nowFunc = () => DateTime.Now;
\r
392 private DateTime _lastReset;
\r
393 private IEnumerable<QuestStatus> _clearedQuest = new List<QuestStatus>();
\r
395 private readonly Color[] _color =
\r
397 Color.FromArgb(60, 141, 76), Color.FromArgb(232, 57, 41), Color.FromArgb(136, 204, 120),
\r
398 Color.FromArgb(52, 147, 185), Color.FromArgb(220, 198, 126), Color.FromArgb(168, 111, 76),
\r
399 Color.FromArgb(200, 148, 231), Color.FromArgb(232, 57, 41)
\r
402 public int AcceptMax { get; set; } = 5;
\r
404 public QuestStatus[] Quests => _quests.Values.ToArray();
\r
406 public QuestInfo(ItemInfo itemInfo, BattleInfo battleInfo, Func<DateTime> nowFunc = null)
\r
408 _itemInfo = itemInfo;
\r
409 _battleInfo = battleInfo;
\r
410 if (nowFunc != null)
\r
411 _nowFunc = nowFunc;
\r
414 public void GetNotifications(out string[] notify, out string[] stop)
\r
416 var cleared = _quests.Values.Where(q => q.Count.Cleared).ToArray();
\r
417 notify = cleared.Except(_clearedQuest, new QuestComparer()).Select(q => q.Name).ToArray();
\r
418 stop = _clearedQuest.Except(cleared, new QuestComparer()).Select(q => q.Name).ToArray();
\r
419 _clearedQuest = cleared;
\r
422 private class QuestComparer : IEqualityComparer<QuestStatus>
\r
424 public bool Equals(QuestStatus x, QuestStatus y)
\r
426 return x?.Id == y?.Id;
\r
429 public int GetHashCode(QuestStatus obj)
\r
435 public void InspectQuestList(dynamic json)
\r
438 if (json.api_list == null)
\r
440 for (var i = 0; i < 2; i++)
\r
442 foreach (var entry in json.api_list)
\r
444 if (entry is double) // -1の場合がある。
\r
447 var id = (int)entry.api_no;
\r
448 var state = (int)entry.api_state;
\r
449 var progress = (int)entry.api_progress_flag;
\r
450 var cat = (int)entry.api_category;
\r
451 var name = (string)entry.api_title;
\r
452 var detail = ((string)entry.api_detail).Replace("<br>", "\r\n");
\r
453 var material = (int[])entry.api_get_material;
\r
469 if (_quests.Remove(id))
\r
476 AddQuest(id, cat, name, detail, material, progress, true);
\r
480 if (_quests.Count <= AcceptMax)
\r
483 * ほかのPCで任務を達成した場合、任務が消えずに受領した任務の数が_questCountを超えることがある。
\r
484 * その場合はいったん任務をクリアして、現在のページの任務だけを登録し直す。
\r
490 private void AddQuest(int id, int category, string name, string detail, int[] material, int progress,
\r
493 var count = _countList.GetCount(id);
\r
496 if (count.AdjustCount(progress))
\r
499 _quests[id] = new QuestStatus
\r
502 Category = category,
\r
505 Material = adjustCount ? material?.Concat(count.Spec.Material).ToArray() : material,
\r
507 Progress = progress,
\r
508 Color = category <= _color.Length ? _color[category - 1] : Control.DefaultBackColor
\r
512 public void ClearQuests()
\r
517 private void ResetQuests()
\r
519 var now = _nowFunc();
\r
520 var daily = now.Date.AddHours(5);
\r
521 if (!(_lastReset < daily && daily <= now))
\r
523 RemoveQuest(QuestInterval.Daily);
\r
524 _countList.Remove(QuestInterval.Daily);
\r
525 var weekly = now.Date.AddDays(-((6 + (int)now.DayOfWeek) % 7)).AddHours(5);
\r
526 if (_lastReset < weekly && weekly <= now)
\r
528 RemoveQuest(QuestInterval.Weekly);
\r
529 _countList.Remove(QuestInterval.Weekly);
\r
531 var monthly = new DateTime(now.Year, now.Month, 1, 5, 0, 0);
\r
532 if (_lastReset < monthly && monthly <= now)
\r
534 RemoveQuest(QuestInterval.Monthly);
\r
535 _countList.Remove(QuestInterval.Monthly);
\r
537 var season = now.Month / 3;
\r
538 var quarterly = new DateTime(now.Year - (season == 0 ? 1 : 0), (season == 0 ? 12 : season * 3), 1, 5, 0, 0);
\r
539 if (_lastReset < quarterly && quarterly <= now)
\r
541 RemoveQuest(QuestInterval.Quarterly);
\r
542 _countList.Remove(QuestInterval.Quarterly);
\r
548 private void RemoveQuest(QuestInterval interval)
\r
551 (from kv in _quests where kv.Value.Count.Spec.Interval == interval select kv.Key).ToArray())
\r
552 _quests.Remove(id);
\r
556 private bool _boss;
\r
558 public void InspectMapStart(dynamic json)
\r
560 if (_quests.TryGetValue(214, out var ago)) // あ号
\r
561 ago.Count.NowArray[0]++;
\r
562 InspectMapNext(json);
\r
565 public void InspectMapNext(dynamic json)
\r
567 _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;
\r
568 _boss = (int)json.api_event_id == 5;
\r
570 if (_quests.TryGetValue(861, out var q861))
\r
572 if (_map == 16 && (int)json.api_event_id == 8)
\r
574 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
576 if (fleet.Count(s => s == 10 || s == 22) == 2)
\r
577 IncrementCount(q861.Count);
\r
582 public void InspectBattleResult(dynamic json)
\r
584 var rank = json.api_win_rank;
\r
585 foreach (var quest in _quests.Values)
\r
587 var count = quest.Count;
\r
588 switch (count.Spec)
\r
590 case QuestSortie sortie:
\r
591 if (count.Id == 216 && !_boss || sortie.Check(rank, _map, _boss))
\r
592 IncrementCount(count);
\r
594 case QuestEnemyType enemyType:
\r
595 var num = enemyType.CountResult(
\r
596 _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));
\r
598 AddCount(count, num);
\r
602 if (_quests.TryGetValue(214, out var ago))
\r
604 var array = ago.Count.NowArray;
\r
608 if (QuestSortie.CompareRank(rank, "B") <= 0)
\r
618 if (_quests.TryGetValue(249, out var q249))
\r
620 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
622 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id)
\r
624 if (fleet.Intersect(new[] {62, 63, 64, 265, 266, 268, 319, 192, 194}).Count() == 3)
\r
625 IncrementCount(q249.Count);
\r
628 if (_quests.TryGetValue(257, out var q257))
\r
630 if (_map == 14 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
632 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
634 if (fleet[0] == 3 && fleet.Count(s => s == 3) <= 3 && fleet.All(s => s == 2 || s == 3))
\r
635 IncrementCount(q257.Count);
\r
638 if (_quests.TryGetValue(259, out var q259))
\r
640 if (_map == 51 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
642 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec).ToArray();
\r
643 // ReSharper disable once IdentifierTypo
\r
651 if (fleet.Select(s => s.ShipClass).Count(c => ctype.Contains(c)) == 3 &&
\r
652 fleet.Count(s => s.ShipType == 3) > 0)
\r
654 IncrementCount(q259.Count);
\r
658 if (_quests.TryGetValue(264, out var q264))
\r
660 if (_map == 42 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
662 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec)
\r
664 if (fleet.Count(spec => spec.ShipType == 2) >= 2 &&
\r
665 fleet.Count(spec => spec.IsAircraftCarrier) >= 2)
\r
666 IncrementCount(q264.Count);
\r
669 if (_quests.TryGetValue(266, out var q266))
\r
671 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
673 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
675 if (fleet[0] == 2 && fleet.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5}))
\r
676 IncrementCount(q266.Count);
\r
679 if (_quests.TryGetValue(854, out var opz) && _boss)
\r
681 var array = opz.Count.NowArray;
\r
684 case 24 when QuestSortie.CompareRank(rank, "A") <= 0:
\r
688 case 61 when QuestSortie.CompareRank(rank, "A") <= 0:
\r
692 case 63 when QuestSortie.CompareRank(rank, "A") <= 0:
\r
696 case 64 when QuestSortie.CompareRank(rank, "S") <= 0:
\r
702 if (_quests.TryGetValue(862, out var q862))
\r
704 if (_map == 63 && _boss && QuestSortie.CompareRank(rank, "A") <= 0)
\r
706 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
708 if (fleet.Count(s => s == 3) >= 2 && fleet.Count(s => s == 16) >= 1)
\r
709 IncrementCount(q862.Count);
\r
712 if (_quests.TryGetValue(873, out var q873))
\r
714 if (_battleInfo.Result.Friend.Main.Count(s => s.NowHp > 0 && s.Spec.ShipType == 3) >= 1 &&
\r
715 _boss && QuestSortie.CompareRank(rank, "A") <= 0)
\r
717 var array = q873.Count.NowArray;
\r
735 if (_quests.TryGetValue(875, out var q875))
\r
737 if (_map == 54 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
739 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id).ToArray();
\r
740 if (fleet.Contains(543) && fleet.Intersect(new[] {344, 345, 359}).Any())
\r
741 IncrementCount(q875.Count);
\r
744 if (_quests.TryGetValue(888, out var q888))
\r
746 if (!_boss || QuestSortie.CompareRank(rank, "S") != 0)
\r
748 var fleet = from ship in _battleInfo.Result.Friend.Main where ship.NowHp > 0 select ship.Spec.Id;
\r
751 69, 272, 427, // 鳥海
\r
753 123, 295, 142, // 衣笠
\r
754 59, 262, 416, // 古鷹
\r
755 60, 263, 417, // 加古
\r
756 51, 213, 477, // 天龍
\r
759 if (fleet.Intersect(member).Count() < 4)
\r
761 var array = q888.Count.NowArray;
\r
780 private int _questFleet;
\r
782 public void StartPractice(string request)
\r
784 var values = HttpUtility.ParseQueryString(request);
\r
785 _questFleet = int.Parse(values["api_deck_id"]) - 1;
\r
788 public void InspectPracticeResult(dynamic json)
\r
790 foreach (var quest in _quests.Values)
\r
792 var count = quest.Count;
\r
793 if (!(count.Spec is QuestPractice practice))
\r
795 if (practice.Check(json.api_win_rank))
\r
796 IncrementCount(count);
\r
798 if (_quests.TryGetValue(318, out var q318))
\r
800 if (_questFleet == 0 && QuestSortie.CompareRank(json.api_win_rank, "B") <= 0 &&
\r
801 _battleInfo.Result.Friend.Main.Count(s => s.Spec.ShipType == 3) >= 2)
\r
803 IncrementCount(q318.Count);
\r
808 private readonly int[] _missionId = new int[ShipInfo.FleetCount];
\r
810 public void InspectDeck(dynamic json)
\r
812 foreach (var entry in json)
\r
813 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];
\r
816 public void InspectMissionResult(string request, dynamic json)
\r
818 var values = HttpUtility.ParseQueryString(request);
\r
819 var deck = int.Parse(values["api_deck_id"]);
\r
820 if ((int)json.api_clear_result == 0)
\r
822 var mid = _missionId[deck - 1];
\r
823 foreach (var quest in _quests.Values)
\r
825 var count = quest.Count;
\r
826 if (!(count.Spec is QuestMission mission))
\r
828 if (mission.Check(mid))
\r
829 IncrementCount(count);
\r
831 if (_quests.TryGetValue(426, out var q426))
\r
833 var count = q426.Count;
\r
837 count.NowArray[0]++;
\r
840 count.NowArray[1]++;
\r
843 count.NowArray[2]++;
\r
846 count.NowArray[3]++;
\r
850 if (_quests.TryGetValue(428, out var q428))
\r
852 var count = q428.Count;
\r
856 count.NowArray[0]++;
\r
859 count.NowArray[1]++;
\r
862 count.NowArray[2]++;
\r
868 private void IncrementCount(QuestCount count)
\r
874 private void AddCount(QuestCount count, int value)
\r
876 count.Now += value;
\r
880 private void IncrementCount(int id)
\r
885 private void AddCount(int id, int value)
\r
887 if (_quests.TryGetValue(id, out var quest))
\r
889 quest.Count.Now += value;
\r
894 public void CountNyukyo() => IncrementCount(503);
\r
896 public void CountCharge() => IncrementCount(504);
\r
898 public void CountCreateItem()
\r
900 IncrementCount(605);
\r
901 IncrementCount(607);
\r
904 public void CountCreateShip()
\r
906 IncrementCount(606);
\r
907 IncrementCount(608);
\r
910 public void InspectDestroyShip(string request)
\r
912 AddCount(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);
\r
915 public void CountRemodelSlot() => IncrementCount(619);
\r
917 public void InspectDestroyItem(string request, dynamic json)
\r
919 var values = HttpUtility.ParseQueryString(request);
\r
920 var items = values["api_slotitem_ids"].Split(',')
\r
921 .Select(id => _itemInfo.GetStatus(int.Parse(id)).Spec).ToArray();
\r
922 IncrementCount(613); // 613: 資源の再利用
\r
923 foreach (var quest in _quests.Values)
\r
925 var count = quest.Count;
\r
926 if (!(count.Spec is QuestDestroyItem destroy))
\r
928 if (destroy.Count(count, items))
\r
931 if (_quests.TryGetValue(680, out var q680))
\r
933 q680.Count.NowArray[0] += items.Count(spec => spec.Type == 21);
\r
934 q680.Count.NowArray[1] += items.Count(spec => spec.Type == 12 || spec.Type == 13);
\r
939 public void InspectPowerUp(dynamic json)
\r
941 if ((int)json.api_powerup_flag == 0)
\r
943 foreach (var quest in _quests.Values)
\r
945 var count = quest.Count;
\r
946 if (!(count.Spec is QuestPowerUp))
\r
948 IncrementCount(count);
\r
952 public void InspectStop(string request)
\r
954 var values = HttpUtility.ParseQueryString(request);
\r
955 _quests.Remove(int.Parse(values["api_quest_id"]));
\r
959 public void InspectClearItemGet(string request)
\r
961 var values = HttpUtility.ParseQueryString(request);
\r
962 var id = int.Parse(values["api_quest_id"]);
\r
963 _countList.Remove(id);
\r
964 _quests.Remove(id);
\r
968 public bool NeedSave { get; private set; }
\r
970 public void SaveState(Status status)
\r
973 status.QuestLastReset = _lastReset;
\r
974 if (_quests != null)
\r
975 status.QuestList = _quests.Values.ToArray();
\r
976 if (_countList != null)
\r
977 status.QuestCountList = _countList.CountList.ToArray();
\r
980 public void LoadState(Status status)
\r
982 _lastReset = status.QuestLastReset;
\r
983 if (status.QuestCountList != null)
\r
984 _countList.CountList = status.QuestCountList;
\r
985 if (status.QuestList != null)
\r
988 foreach (var q in status.QuestList)
\r
989 AddQuest(q.Id, q.Category, q.Name, q.Detail, q.Material, q.Progress, false);
\r