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 || Id == 894)
\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
235 return string.Join(" ",
\r
236 new[] {"1-5", "7-1", "7-2G", "7-2M"}.Zip(NowArray, (map, n) => n >= 1 ? $"{map}:{n}" : "")
\r
237 .Where(s => !string.IsNullOrEmpty(s)));
\r
239 return string.Join(" ",
\r
240 new[] {"1-3", "1-4", "2-1", "2-2", "2-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")
\r
241 .Where(s => !string.IsNullOrEmpty(s)));
\r
246 public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ?? Spec.Max != 0 && Now >= Spec.Max;
\r
250 public class QuestCountList
\r
252 private const QuestInterval Daily = QuestInterval.Daily;
\r
253 private const QuestInterval Weekly = QuestInterval.Weekly;
\r
254 private const QuestInterval Monthly = QuestInterval.Monthly;
\r
255 private const QuestInterval Quarterly = QuestInterval.Quarterly;
\r
258 /// このテーブルは七四式電子観測儀を参考に作成した。
\r
259 /// https://github.com/andanteyk/ElectronicObserver/blob/develop/ElectronicObserver/Data/Quest/QuestProgressManager.cs
\r
261 private static readonly Dictionary<int, QuestSpec> QuestSpecs = new Dictionary<int, QuestSpec>
\r
263 {201, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 0, 1, 0}}}, // 201: 敵艦隊を撃滅せよ!
\r
264 {216, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 1, 1, 0}}}, // 216: 敵艦隊主力を撃滅せよ!
\r
265 {210, new QuestSortie {Interval = Daily, Max = 10, Material = new[] {0, 0, 1, 0}}}, // 210: 敵艦隊を10回邀撃せよ!
\r
266 {211, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {7, 11}, Material = new[] {0, 2, 0, 0}}}, // 211: 敵空母を3隻撃沈せよ!
\r
267 {212, new QuestEnemyType {Interval = Daily, Max = 5, EnemyType = new[] {15}, Material = new[] {0, 0, 2, 0}}}, // 212: 敵輸送船団を叩け!
\r
268 {218, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {15}, Material = new[] {0, 1, 1, 0}}}, // 218: 敵補給艦を3隻撃沈せよ!
\r
269 {226, new QuestSortie {Interval = Daily, Max = 5, Rank = "B", Maps = new[] {21, 22, 23, 24, 25}, Material = new[] {1, 1, 0, 0}}}, // 226: 南西諸島海域の制海権を握れ!
\r
270 {230, new QuestEnemyType {Interval = Daily, Max = 6, EnemyType = new[] {13}, Material = new[] {0, 1, 0, 0}}}, // 230: 敵潜水艦を制圧せよ!
\r
272 {213, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {15}, Material = new[] {0, 0, 3, 0}}}, // 213: 海上通商破壊作戦
\r
273 {214, new QuestSpec {Interval = Weekly, MaxArray = new[] {36, 6, 24, 12}, Material = new[] {2, 0, 2, 0}}}, // 214: あ号作戦
\r
274 {220, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {7, 11}, Material = new[] {0, 0, 2, 0}}}, // 220: い号作戦
\r
275 {221, new QuestEnemyType {Interval = Weekly, Max = 50, EnemyType = new[] {15}, Material = new[] {0, 3, 0, 0}}}, // 221: ろ号作戦
\r
276 {228, new QuestEnemyType {Interval = Weekly, Max = 15, EnemyType = new[] {13}, Material = new[] {0, 2, 0, 1}}}, // 228: 海上護衛戦
\r
277 {229, new QuestSortie {Interval = Weekly, Max = 12, Rank = "B", Maps = new[] {41, 42, 43, 44, 45}, Material = new[] {0, 0, 2, 0}}}, // 229: 敵東方艦隊を撃滅せよ!
\r
278 {241, new QuestSortie {Interval = Weekly, Max = 5, Rank = "B", Maps = new[] {33, 34, 35}, Material = new[] {0, 0, 3, 3}}}, // 241: 敵北方艦隊主力を撃滅せよ!
\r
279 {242, new QuestSortie {Interval = Weekly, Max = 1, Rank = "B", Maps = new[] {44}, Material = new[] {0, 1, 1, 0}}}, // 242: 敵東方中枢艦隊を撃破せよ!
\r
280 {243, new QuestSortie {Interval = Weekly, Max = 2, Rank = "S", Maps = new[] {52}, Material = new[] {0, 0, 2, 2}}}, // 243: 南方海域珊瑚諸島沖の制空権を握れ!
\r
281 {249, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 5, 0}}}, // 249: 「第五戦隊」出撃せよ!
\r
282 {256, new QuestSortie {Interval = Monthly, Max = 3, Rank = "S", Maps = new[] {61}, Material = new[] {0, 0, 0, 0}}}, // 256: 「潜水艦隊」出撃せよ!
\r
283 {257, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 0, 3}}}, // 257: 「水雷戦隊」南西へ!
\r
284 {259, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 3, 0, 4}}}, // 259: 「水上打撃部隊」南方へ!
\r
285 {261, new QuestSortie {Interval = Weekly, Max = 3, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 0, 3}}}, // 261: 海上輸送路の安全確保に努めよ!
\r
286 {264, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 0, 2}}}, // 264: 「空母機動部隊」西へ!
\r
287 {265, new QuestSortie {Interval = Monthly, Max = 10, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 5, 3}}}, // 265: 海上護衛強化月間
\r
288 {266, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 4, 2}}}, // 266: 「水上反撃部隊」突入せよ!
\r
290 {822, new QuestSortie {Interval = Quarterly, Max = 2, Rank = "S", Maps = new[] {24}, Material = new[] {0, 0, 0, 5}}}, // 822: 沖ノ島海域迎撃戦
\r
291 {854, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 0, 4}}}, // 854: 戦果拡張任務!「Z作戦」前段作戦
\r
292 {861, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 4, 0, 0}}}, // 861: 強行輸送艦隊、抜錨!
\r
293 {862, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 8, 4}}}, // 862: 前線の航空偵察を実施せよ!
\r
294 {873, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1}, Material = new[] {0, 0, 0, 0}}}, // 873: 北方海域警備を実施せよ!
\r
295 {875, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 0, 0}}}, // 875: 精鋭「三一駆」、鉄底海域に突入せよ!
\r
296 {888, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1}, Material = new[] {0, 0, 0, 0}}}, // 888: 新編成「三川艦隊」、鉄底海峡に突入せよ!
\r
297 {893, new QuestSpec {Interval = Quarterly, MaxArray = new[] {3, 3, 3, 3}, Material = new[] {0, 0, 0, 0}}}, // 893: 泊地周辺海域の安全確保を徹底せよ!
\r
298 {894, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1, 1}, Material = new[] {0, 0, 0, 0}}}, // 894: 空母戦力の投入による兵站線戦闘哨戒
\r
300 {303, new QuestPractice {Interval = Daily, Max = 3, Win = false, Material = new[] {1, 0, 0, 0}}}, // 303: 「演習」で練度向上!
\r
301 {304, new QuestPractice {Interval = Daily, Max = 5, Win = true, Material = new[] {0, 0, 1, 0}}}, // 304: 「演習」で他提督を圧倒せよ!
\r
302 {302, new QuestPractice {Interval = Weekly, Max = 20, Win = true, Material = new[] {0, 0, 2, 1}}}, // 302: 大規模演習
\r
303 {311, new QuestPractice {Interval = Daily, Max = 7, Win = true, Material = new[] {0, 2, 0, 0}}}, // 311: 精鋭艦隊演習
\r
304 {315, new QuestPractice {Interval = Daily, Max = 8, Win = true, Material = new[] {0, 0, 0, 0}}}, // 315: 春季大演習
\r
305 {318, new QuestSpec {Interval = Daily, Max = 3, Material = new[] {0, 2, 2, 0}, AdjustCount = false}}, // 318: 給糧艦「伊良湖」の支援
\r
307 {402, new QuestMission {Interval = Daily, Max = 3, Material = new[] {0, 0, 1, 0}}}, // 402: 「遠征」を3回成功させよう!
\r
308 {403, new QuestMission {Interval = Daily, Max = 10, Material = new[] {0, 0, 0, 0}}}, // 403: 「遠征」を10回成功させよう!
\r
309 {404, new QuestMission {Interval = Weekly, Max = 30, Material = new[] {0, 0, 3, 0}}}, // 404: 大規模遠征作戦、発令!
\r
310 {410, new QuestMission {Interval = Weekly, Max = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 0, 0}}}, // 410: 南方への輸送作戦を成功させよ!
\r
311 {411, new QuestMission {Interval = Weekly, Max = 6, Shift = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 2, 1}}}, // 411: 南方への鼠輸送を継続実施せよ!
\r
312 {424, new QuestMission {Interval = Monthly, Max = 4, Shift = 1, Ids = new[] {5}, Material = new[] {0, 0, 0, 0}}}, // 424: 輸送船団護衛を強化せよ!
\r
313 {426, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 4, 0}}}, // 426: 海上通商航路の警戒を厳とせよ!
\r
314 {428, new QuestSpec {Interval = Quarterly, MaxArray = new[] {2, 2, 2}, Material = new[] {0, 0, 0, 3}}}, // 428: 近海に侵入する敵潜を制圧せよ!
\r
316 {503, new QuestSpec {Interval = Daily, Max = 5, Material = new[] {0, 2, 0, 0}}}, // 503: 艦隊大整備!
\r
317 {504, new QuestSpec {Interval = Daily, Max = 15, Material = new[] {1, 0, 1, 0}}}, // 504: 艦隊酒保祭り!
\r
319 {605, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {1, 0, 1, 0}}}, // 605: 新装備「開発」指令
\r
320 {606, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 1, 1, 0}}}, // 606: 新造艦「建造」指令
\r
321 {607, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {0, 0, 2, 0}}}, // 607: 装備「開発」集中強化!
\r
322 {608, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {1, 0, 2, 0}}}, // 608: 艦娘「建造」艦隊強化!
\r
323 {609, new QuestSpec {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 609: 軍縮条約対応!
\r
324 {619, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 0, 0, 1}}}, // 619: 装備の改修強化
\r
326 {613, new QuestSpec {Interval = Weekly, Max = 24, Material = new[] {0, 0, 0, 0}}}, // 613: 資源の再利用
\r
327 {638, new QuestDestroyItem {Interval = Weekly, Max = 6, Types = new[] {21}, Material = new[] {0, 0, 2, 1}}}, // 638: 対空機銃量産
\r
328 {643, new QuestDestroyItem {Interval = Quarterly, Max = 2, Ids = new[] {20}, Material = new[] {0, 0, 2, 0}, AdjustCount = false}}, // 643: 主力「陸攻」の調達
\r
329 {645, new QuestDestroyItem {Interval = Monthly, Max = 1, Ids = new[] {35}, Material = new[] {0, 0, 0, 0}, AdjustCount = false}}, // 645: 「洋上補給」物資の調達
\r
330 {663, new QuestDestroyItem {Interval = Quarterly, Max = 10, Types = new[] {3}, Material = new[] {0, 0, 3, 0}}}, // 663: 新型艤装の継続研究
\r
331 {673, new QuestDestroyItem {Interval = Daily, Max = 4, Types = new[] {1}, Shift = 1, Material = new[] {0, 0, 1, 0}}}, // 673: 装備開発力の整備
\r
332 {674, new QuestDestroyItem {Interval = Daily, Max = 3, Types = new[] {21}, Shift = 2, Material = new[] {0, 1, 1, 0}}}, // 674: 工廠環境の整備
\r
333 {675, new QuestDestroyItem {Interval = Quarterly, MaxArray = new[] {6, 4}, Types = new[] {6, 21}, Material = new[] {0, 0, 0, 0}}}, // 675: 運用装備の統合整備
\r
334 {676, new QuestDestroyItem {Interval = Weekly, MaxArray = new[] {3, 3, 1}, Types = new[] {2, 4, 30}, Material = new[] {0, 1, 7, 0}}}, // 676: 装備開発力の集中整備
\r
335 {677, new QuestDestroyItem {Interval = Weekly, MaxArray = new[] {4, 2, 3}, Types = new[] {3, 10, 5}, Material = new[] {0, 5, 0, 0}}}, // 677: 継戦支援能力の整備
\r
336 {678, new QuestDestroyItem {Interval = Quarterly, MaxArray = new[] {3, 5}, Ids = new[] {19, 20}, Material = new[] {0, 0, 8, 0}}}, // 678: 主力艦上戦闘機の更新
\r
337 {680, new QuestSpec {Interval = Quarterly, MaxArray = new[] {4, 4}, Material = new[] {0, 0, 6, 0}}}, // 680: 対空兵装の整備拡充
\r
338 {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
340 {702, new QuestPowerUp {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 702: 艦の「近代化改修」を実施せよ!
\r
341 {703, new QuestPowerUp {Interval = Weekly, Max = 15, Material = new[] {1, 0, 2, 0}}} // 703: 「近代化改修」を進め、戦備を整えよ!
\r
345 private readonly Dictionary<int, QuestCount> _countDict = new Dictionary<int, QuestCount>();
\r
347 public QuestCount GetCount(int id)
\r
349 if (_countDict.TryGetValue(id, out var value))
\r
351 if (QuestSpecs.TryGetValue(id, out var spec))
\r
353 var nowArray = spec.MaxArray?.Select(x => 0).ToArray();
\r
354 return _countDict[id] = new QuestCount
\r
358 NowArray = nowArray,
\r
362 return new QuestCount {Spec = new QuestSpec {Material = new int[0], AdjustCount = false}};
\r
365 public void Remove(int id)
\r
367 _countDict.Remove(id);
\r
370 public void Remove(QuestInterval interval)
\r
373 _countDict.Where(pair => pair.Value.Spec.Interval == interval).Select(pair => pair.Key).ToArray())
\r
375 _countDict.Remove(id);
\r
379 public IEnumerable<QuestCount> CountList
\r
381 get => _countDict.Values.Where(c => c.Now > 0 || (c.NowArray?.Any(n => n > 0) ?? false));
\r
386 foreach (var count in value)
\r
388 count.Spec = QuestSpecs[count.Id];
\r
389 _countDict[count.Id] = count;
\r
395 public class QuestInfo : IHaveState
\r
397 private readonly SortedDictionary<int, QuestStatus> _quests = new SortedDictionary<int, QuestStatus>();
\r
398 private readonly QuestCountList _countList = new QuestCountList();
\r
399 private readonly ItemInfo _itemInfo;
\r
400 private readonly BattleInfo _battleInfo;
\r
401 private readonly Func<DateTime> _nowFunc = () => DateTime.Now;
\r
402 private DateTime _lastReset;
\r
403 private IEnumerable<QuestStatus> _clearedQuest = new List<QuestStatus>();
\r
405 private readonly Color[] _color =
\r
407 Color.FromArgb(60, 141, 76), Color.FromArgb(232, 57, 41), Color.FromArgb(136, 204, 120),
\r
408 Color.FromArgb(52, 147, 185), Color.FromArgb(220, 198, 126), Color.FromArgb(168, 111, 76),
\r
409 Color.FromArgb(200, 148, 231), Color.FromArgb(232, 57, 41)
\r
412 public int AcceptMax { get; set; } = 5;
\r
414 public QuestStatus[] Quests => _quests.Values.ToArray();
\r
416 public QuestInfo(ItemInfo itemInfo, BattleInfo battleInfo, Func<DateTime> nowFunc = null)
\r
418 _itemInfo = itemInfo;
\r
419 _battleInfo = battleInfo;
\r
420 if (nowFunc != null)
\r
421 _nowFunc = nowFunc;
\r
424 public void GetNotifications(out string[] notify, out string[] stop)
\r
426 var cleared = _quests.Values.Where(q => q.Count.Cleared).ToArray();
\r
427 notify = cleared.Except(_clearedQuest, new QuestComparer()).Select(q => q.Name).ToArray();
\r
428 stop = _clearedQuest.Except(cleared, new QuestComparer()).Select(q => q.Name).ToArray();
\r
429 _clearedQuest = cleared;
\r
432 private class QuestComparer : IEqualityComparer<QuestStatus>
\r
434 public bool Equals(QuestStatus x, QuestStatus y)
\r
436 return x?.Id == y?.Id;
\r
439 public int GetHashCode(QuestStatus obj)
\r
445 public void InspectQuestList(dynamic json)
\r
448 if (json.api_list == null)
\r
450 for (var i = 0; i < 2; i++)
\r
452 foreach (var entry in json.api_list)
\r
454 if (entry is double) // -1の場合がある。
\r
457 var id = (int)entry.api_no;
\r
458 var state = (int)entry.api_state;
\r
459 var progress = (int)entry.api_progress_flag;
\r
460 var cat = (int)entry.api_category;
\r
461 var name = (string)entry.api_title;
\r
462 var detail = ((string)entry.api_detail).Replace("<br>", "\r\n");
\r
463 var material = (int[])entry.api_get_material;
\r
479 if (_quests.Remove(id))
\r
486 AddQuest(id, cat, name, detail, material, progress, true);
\r
490 if (_quests.Count <= AcceptMax)
\r
493 * ほかのPCで任務を達成した場合、任務が消えずに受領した任務の数が_questCountを超えることがある。
\r
494 * その場合はいったん任務をクリアして、現在のページの任務だけを登録し直す。
\r
500 private void AddQuest(int id, int category, string name, string detail, int[] material, int progress,
\r
503 var count = _countList.GetCount(id);
\r
506 if (count.AdjustCount(progress))
\r
509 _quests[id] = new QuestStatus
\r
512 Category = category,
\r
515 Material = adjustCount ? material?.Concat(count.Spec.Material).ToArray() : material,
\r
517 Progress = progress,
\r
518 Color = category <= _color.Length ? _color[category - 1] : Control.DefaultBackColor
\r
522 public void ClearQuests()
\r
527 private void ResetQuests()
\r
529 var now = _nowFunc();
\r
530 var daily = now.Date.AddHours(5);
\r
531 if (!(_lastReset < daily && daily <= now))
\r
533 RemoveQuest(QuestInterval.Daily);
\r
534 _countList.Remove(QuestInterval.Daily);
\r
535 var weekly = now.Date.AddDays(-((6 + (int)now.DayOfWeek) % 7)).AddHours(5);
\r
536 if (_lastReset < weekly && weekly <= now)
\r
538 RemoveQuest(QuestInterval.Weekly);
\r
539 _countList.Remove(QuestInterval.Weekly);
\r
541 var monthly = new DateTime(now.Year, now.Month, 1, 5, 0, 0);
\r
542 if (_lastReset < monthly && monthly <= now)
\r
544 RemoveQuest(QuestInterval.Monthly);
\r
545 _countList.Remove(QuestInterval.Monthly);
\r
547 var season = now.Month / 3;
\r
548 var quarterly = new DateTime(now.Year - (season == 0 ? 1 : 0), (season == 0 ? 12 : season * 3), 1, 5, 0, 0);
\r
549 if (_lastReset < quarterly && quarterly <= now)
\r
551 RemoveQuest(QuestInterval.Quarterly);
\r
552 _countList.Remove(QuestInterval.Quarterly);
\r
558 private void RemoveQuest(QuestInterval interval)
\r
561 (from kv in _quests where kv.Value.Count.Spec.Interval == interval select kv.Key).ToArray())
\r
562 _quests.Remove(id);
\r
567 private bool _boss;
\r
569 public void InspectMapStart(dynamic json)
\r
571 if (_quests.TryGetValue(214, out var ago)) // あ号
\r
572 ago.Count.NowArray[0]++;
\r
573 InspectMapNext(json);
\r
576 public void InspectMapNext(dynamic json)
\r
578 _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;
\r
579 _cell = json.api_no() ? (int)json.api_no : 0;
\r
580 _boss = (int)json.api_event_id == 5;
\r
582 if (_quests.TryGetValue(861, out var q861))
\r
584 if (_map == 16 && (int)json.api_event_id == 8)
\r
586 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
588 if (fleet.Count(s => s == 10 || s == 22) == 2)
\r
589 IncrementCount(q861.Count);
\r
594 public void InspectBattleResult(dynamic json)
\r
596 var rank = json.api_win_rank;
\r
597 foreach (var quest in _quests.Values)
\r
599 var count = quest.Count;
\r
600 switch (count.Spec)
\r
602 case QuestSortie sortie:
\r
603 if (count.Id == 216 && !_boss || sortie.Check(rank, _map, _boss))
\r
604 IncrementCount(count);
\r
606 case QuestEnemyType enemyType:
\r
607 var num = enemyType.CountResult(
\r
608 _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));
\r
610 AddCount(count, num);
\r
614 if (_quests.TryGetValue(214, out var ago))
\r
616 var count = ago.Count;
\r
619 IncrementNowArray(count, 2);
\r
620 if (QuestSortie.CompareRank(rank, "B") <= 0)
\r
621 IncrementNowArray(count, 3);
\r
624 IncrementNowArray(count, 1);
\r
626 if (_quests.TryGetValue(249, out var q249))
\r
628 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
630 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id)
\r
632 if (fleet.Intersect(new[] {62, 63, 64, 265, 266, 268, 319, 192, 194}).Count() == 3)
\r
633 IncrementCount(q249.Count);
\r
636 if (_quests.TryGetValue(257, out var q257))
\r
638 if (_map == 14 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
640 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
642 if (fleet[0] == 3 && fleet.Count(s => s == 3) <= 3 && fleet.All(s => s == 2 || s == 3))
\r
643 IncrementCount(q257.Count);
\r
646 if (_quests.TryGetValue(259, out var q259))
\r
648 if (_map == 51 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
650 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec).ToArray();
\r
651 // ReSharper disable once IdentifierTypo
\r
659 if (fleet.Select(s => s.ShipClass).Count(c => ctype.Contains(c)) == 3 &&
\r
660 fleet.Count(s => s.ShipType == 3) > 0)
\r
662 IncrementCount(q259.Count);
\r
666 if (_quests.TryGetValue(264, out var q264))
\r
668 if (_map == 42 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
670 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec)
\r
672 if (fleet.Count(spec => spec.ShipType == 2) >= 2 &&
\r
673 fleet.Count(spec => spec.IsAircraftCarrier) >= 2)
\r
674 IncrementCount(q264.Count);
\r
677 if (_quests.TryGetValue(266, out var q266))
\r
679 if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
681 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
683 if (fleet[0] == 2 && fleet.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5}))
\r
684 IncrementCount(q266.Count);
\r
687 if (_quests.TryGetValue(854, out var opz) && _boss)
\r
689 var count = opz.Count;
\r
692 case 24 when QuestSortie.CompareRank(rank, "A") <= 0:
\r
693 IncrementNowArray(count, 0);
\r
695 case 61 when QuestSortie.CompareRank(rank, "A") <= 0:
\r
696 IncrementNowArray(count, 1);
\r
698 case 63 when QuestSortie.CompareRank(rank, "A") <= 0:
\r
699 IncrementNowArray(count, 2);
\r
702 case 64 when QuestSortie.CompareRank(rank, "S") <= 0:
\r
703 IncrementNowArray(count, 3);
\r
707 if (_quests.TryGetValue(862, out var q862))
\r
709 if (_map == 63 && _boss && QuestSortie.CompareRank(rank, "A") <= 0)
\r
711 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)
\r
713 if (fleet.Count(s => s == 3) >= 2 && fleet.Count(s => s == 16) >= 1)
\r
714 IncrementCount(q862.Count);
\r
717 if (_quests.TryGetValue(873, out var q873))
\r
719 if (_battleInfo.Result.Friend.Main.Count(s => s.NowHp > 0 && s.Spec.ShipType == 3) >= 1 &&
\r
720 _boss && QuestSortie.CompareRank(rank, "A") <= 0)
\r
722 var count = q873.Count;
\r
726 IncrementNowArray(count, 0);
\r
729 IncrementNowArray(count, 1);
\r
732 IncrementNowArray(count, 2);
\r
737 if (_quests.TryGetValue(875, out var q875))
\r
739 if (_map == 54 && _boss && QuestSortie.CompareRank(rank, "S") == 0)
\r
741 var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id).ToArray();
\r
742 if (fleet.Contains(543) && fleet.Intersect(new[] {344, 345, 359}).Any())
\r
743 IncrementCount(q875.Count);
\r
746 if (_quests.TryGetValue(888, out var q888))
\r
748 if (!_boss || QuestSortie.CompareRank(rank, "S") != 0)
\r
750 var fleet = from ship in _battleInfo.Result.Friend.Main where ship.NowHp > 0 select ship.Spec.Id;
\r
753 69, 272, 427, // 鳥海
\r
755 123, 295, 142, // 衣笠
\r
756 59, 262, 416, // 古鷹
\r
757 60, 263, 417, // 加古
\r
758 51, 213, 477, // 天龍
\r
761 if (fleet.Intersect(member).Count() < 4)
\r
763 var count = q888.Count;
\r
767 IncrementNowArray(count, 0);
\r
770 IncrementNowArray(count, 1);
\r
773 IncrementNowArray(count, 2);
\r
777 if (_quests.TryGetValue(893, out var q893))
\r
779 if (QuestSortie.CompareRank(rank, "S") != 0)
\r
781 var count = q893.Count;
\r
784 if (_map == 72 && _cell == 9)
\r
786 IncrementNowArray(count, 2);
\r
793 IncrementNowArray(count, 0);
\r
796 IncrementNowArray(count, 1);
\r
799 IncrementNowArray(count, 3);
\r
803 if (_quests.TryGetValue(894, out var q894))
\r
806 QuestSortie.CompareRank(rank, "S") != 0 ||
\r
807 !_battleInfo.Result.Friend.Main.Any(s => s.Spec.IsAircraftCarrier && s.NowHp > 0))
\r
809 var count = q894.Count;
\r
813 IncrementNowArray(count, 0);
\r
816 IncrementNowArray(count, 1);
\r
819 IncrementNowArray(count, 2);
\r
822 IncrementNowArray(count, 3);
\r
825 IncrementNowArray(count, 4);
\r
831 private int _questFleet;
\r
833 public void StartPractice(string request)
\r
835 var values = HttpUtility.ParseQueryString(request);
\r
836 _questFleet = int.Parse(values["api_deck_id"]) - 1;
\r
839 public void InspectPracticeResult(dynamic json)
\r
841 foreach (var quest in _quests.Values)
\r
843 var count = quest.Count;
\r
844 if (!(count.Spec is QuestPractice practice))
\r
846 if (practice.Check(json.api_win_rank))
\r
847 IncrementCount(count);
\r
849 if (_quests.TryGetValue(318, out var q318))
\r
851 if (_questFleet == 0 && QuestSortie.CompareRank(json.api_win_rank, "B") <= 0 &&
\r
852 _battleInfo.Result.Friend.Main.Count(s => s.Spec.ShipType == 3) >= 2)
\r
854 IncrementCount(q318.Count);
\r
859 private readonly int[] _missionId = new int[ShipInfo.FleetCount];
\r
861 public void InspectDeck(dynamic json)
\r
863 foreach (var entry in json)
\r
864 _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];
\r
867 public void InspectMissionResult(string request, dynamic json)
\r
869 var values = HttpUtility.ParseQueryString(request);
\r
870 var deck = int.Parse(values["api_deck_id"]);
\r
871 if ((int)json.api_clear_result == 0)
\r
873 var mid = _missionId[deck - 1];
\r
874 foreach (var quest in _quests.Values)
\r
876 var count = quest.Count;
\r
877 if (!(count.Spec is QuestMission mission))
\r
879 if (mission.Check(mid))
\r
880 IncrementCount(count);
\r
882 if (_quests.TryGetValue(426, out var q426))
\r
884 var count = q426.Count;
\r
888 IncrementNowArray(count, 0);
\r
891 IncrementNowArray(count, 1);
\r
894 IncrementNowArray(count, 2);
\r
897 IncrementNowArray(count, 3);
\r
901 if (_quests.TryGetValue(428, out var q428))
\r
903 var count = q428.Count;
\r
907 IncrementNowArray(count, 0);
\r
910 IncrementNowArray(count, 1);
\r
913 IncrementNowArray(count, 2);
\r
919 private void IncrementCount(QuestCount count)
\r
925 private void AddCount(QuestCount count, int value)
\r
927 count.Now += value;
\r
931 private void IncrementCount(int id)
\r
936 private void AddCount(int id, int value)
\r
938 if (_quests.TryGetValue(id, out var quest))
\r
940 quest.Count.Now += value;
\r
945 private void IncrementNowArray(QuestCount count, int n)
\r
947 count.NowArray[n]++;
\r
951 public void CountNyukyo() => IncrementCount(503);
\r
953 public void CountCharge() => IncrementCount(504);
\r
955 public void CountCreateItem()
\r
957 IncrementCount(605);
\r
958 IncrementCount(607);
\r
961 public void CountCreateShip()
\r
963 IncrementCount(606);
\r
964 IncrementCount(608);
\r
967 public void InspectDestroyShip(string request)
\r
969 AddCount(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);
\r
972 public void CountRemodelSlot() => IncrementCount(619);
\r
974 public void InspectDestroyItem(string request, dynamic json)
\r
976 var values = HttpUtility.ParseQueryString(request);
\r
977 var items = values["api_slotitem_ids"].Split(',')
\r
978 .Select(id => _itemInfo.GetStatus(int.Parse(id)).Spec).ToArray();
\r
979 IncrementCount(613); // 613: 資源の再利用
\r
980 foreach (var quest in _quests.Values)
\r
982 var count = quest.Count;
\r
983 if (!(count.Spec is QuestDestroyItem destroy))
\r
985 if (destroy.Count(count, items))
\r
988 if (_quests.TryGetValue(680, out var q680))
\r
990 q680.Count.NowArray[0] += items.Count(spec => spec.Type == 21);
\r
991 q680.Count.NowArray[1] += items.Count(spec => spec.Type == 12 || spec.Type == 13);
\r
996 public void InspectPowerUp(dynamic json)
\r
998 if ((int)json.api_powerup_flag == 0)
\r
1000 foreach (var quest in _quests.Values)
\r
1002 var count = quest.Count;
\r
1003 if (!(count.Spec is QuestPowerUp))
\r
1005 IncrementCount(count);
\r
1009 public void InspectStop(string request)
\r
1011 var values = HttpUtility.ParseQueryString(request);
\r
1012 _quests.Remove(int.Parse(values["api_quest_id"]));
\r
1016 public void InspectClearItemGet(string request)
\r
1018 var values = HttpUtility.ParseQueryString(request);
\r
1019 var id = int.Parse(values["api_quest_id"]);
\r
1020 _countList.Remove(id);
\r
1021 _quests.Remove(id);
\r
1025 public bool NeedSave { get; private set; }
\r
1027 public void SaveState(Status status)
\r
1030 status.QuestLastReset = _lastReset;
\r
1031 if (_quests != null)
\r
1032 status.QuestList = _quests.Values.ToArray();
\r
1033 if (_countList != null)
\r
1034 status.QuestCountList = _countList.CountList.ToArray();
\r
1037 public void LoadState(Status status)
\r
1039 _lastReset = status.QuestLastReset;
\r
1040 if (status.QuestCountList != null)
\r
1041 _countList.CountList = status.QuestCountList;
\r
1042 if (status.QuestList != null)
\r
1045 foreach (var q in status.QuestList)
\r
1046 AddQuest(q.Id, q.Category, q.Name, q.Detail, q.Material, q.Progress, false);
\r