OSDN Git Service

QuestCounterの見通しをよくする
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / QuestInfo.cs
1 // Copyright (C) 2013, 2015 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.Drawing;\r
18 using System.Linq;\r
19 using System.Windows.Forms;\r
20 using System.Xml.Serialization;\r
21 using KancolleSniffer.Util;\r
22 \r
23 namespace KancolleSniffer.Model\r
24 {\r
25     public class QuestStatus\r
26     {\r
27         public int Id { get; set; }\r
28         public int Category { get; set; }\r
29         public QuestInterval Interval { 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
34 \r
35         [XmlIgnore]\r
36         public QuestCount Count { get; set; }\r
37 \r
38         [XmlIgnore]\r
39         public Color Color { get; set; }\r
40 \r
41         public string ToToolTip() =>\r
42             Detail +\r
43             (Material == null || Material.All(x => x == 0)\r
44                 ? ""\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
49 \r
50         public QuestStatus Clone()\r
51         {\r
52             var clone = (QuestStatus)MemberwiseClone();\r
53             clone.Count = Count.Clone();\r
54             return clone;\r
55         }\r
56     }\r
57 \r
58     public enum QuestInterval\r
59     {\r
60         // ReSharper disable once UnusedMember.Global\r
61         Other,\r
62         Daily,\r
63         Weekly,\r
64         Monthly,\r
65         Quarterly\r
66     }\r
67 \r
68     public class QuestInfo : IHaveState\r
69     {\r
70         private readonly SortedDictionary<int, QuestStatus> _quests = new SortedDictionary<int, QuestStatus>();\r
71         private readonly QuestCountList _countList = new QuestCountList();\r
72         private readonly Func<DateTime> _nowFunc = () => DateTime.Now;\r
73         private DateTime _now;\r
74         private DateTime _lastReset;\r
75         private IEnumerable<QuestStatus> _clearedQuest = new List<QuestStatus>();\r
76 \r
77         private readonly Color[] _color =\r
78         {\r
79             Color.FromArgb(60, 141, 76), Color.FromArgb(232, 57, 41), Color.FromArgb(136, 204, 120),\r
80             Color.FromArgb(52, 147, 185), Color.FromArgb(220, 198, 126), Color.FromArgb(168, 111, 76),\r
81             Color.FromArgb(200, 148, 231), Color.FromArgb(232, 57, 41)\r
82         };\r
83 \r
84         public int AcceptMax { get; set; } = 5;\r
85 \r
86         public SortedDictionary<int, QuestStatus> QuestDictionary => _quests;\r
87 \r
88         public QuestStatus[] Quests => _quests.Values.ToArray();\r
89 \r
90         public QuestInfo(Func<DateTime> nowFunc = null)\r
91         {\r
92             if (nowFunc != null)\r
93                 _nowFunc = nowFunc;\r
94         }\r
95 \r
96         public void GetNotifications(out string[] notify, out string[] stop)\r
97         {\r
98             var cleared = _quests.Values.Where(q => q.Count.Cleared).ToArray();\r
99             notify = cleared.Except(_clearedQuest, new QuestComparer()).Select(q => q.Name).ToArray();\r
100             stop = _clearedQuest.Except(cleared, new QuestComparer()).Select(q => q.Name).ToArray();\r
101             _clearedQuest = cleared;\r
102         }\r
103 \r
104         private class QuestComparer : IEqualityComparer<QuestStatus>\r
105         {\r
106             public bool Equals(QuestStatus x, QuestStatus y)\r
107             {\r
108                 return x?.Id == y?.Id;\r
109             }\r
110 \r
111             public int GetHashCode(QuestStatus obj)\r
112             {\r
113                 return obj.Id;\r
114             }\r
115         }\r
116 \r
117         private readonly QuestInterval[] _intervals =\r
118         {\r
119             QuestInterval.Daily, QuestInterval.Weekly, QuestInterval.Monthly,\r
120             QuestInterval.Other, QuestInterval.Quarterly\r
121         };\r
122 \r
123         private readonly int[] _progress = {0, 50, 80};\r
124 \r
125         public void InspectQuestList(dynamic json)\r
126         {\r
127             ResetQuests();\r
128             if (json.api_list == null)\r
129                 return;\r
130             for (var i = 0; i < 2; i++)\r
131             {\r
132                 foreach (var entry in json.api_list)\r
133                 {\r
134                     if (entry is double) // -1の場合がある。\r
135                         continue;\r
136                     var quest = new QuestStatus\r
137                     {\r
138                         Id = (int)entry.api_no,\r
139                         Category = (int)entry.api_category,\r
140                         Progress = _progress[(int)entry.api_progress_flag],\r
141                         Interval = _intervals[(int)entry.api_type - 1],\r
142                         Name = (string)entry.api_title,\r
143                         Detail = ((string)entry.api_detail).Replace("<br>", "\r\n"),\r
144                         Material = (int[])entry.api_get_material\r
145                     };\r
146                     var state = (int)entry.api_state;\r
147                     switch (state)\r
148                     {\r
149                         case 1:\r
150                             if (_quests.Remove(quest.Id))\r
151                                 NeedSave = true;\r
152                             break;\r
153                         case 3:\r
154                             quest.Progress = 100;\r
155                             goto case 2;\r
156                         case 2:\r
157                             SetProcessedQuest(quest);\r
158                             break;\r
159                     }\r
160                 }\r
161                 if (_quests.Count <= AcceptMax)\r
162                     break;\r
163                 /*\r
164                  * ほかのPCで任務を達成した場合、任務が消えずに受領した任務の数がAcceptMaxを超えることがある。\r
165                  * その場合はいったん任務をクリアして、現在のページの任務だけを登録し直す。\r
166                  */\r
167                 _quests.Clear();\r
168             }\r
169         }\r
170 \r
171         private void SetProcessedQuest(QuestStatus quest)\r
172         {\r
173             var count = _countList.GetCount(quest.Id);\r
174             if (count.AdjustCount(quest.Progress))\r
175                 NeedSave = true;\r
176             quest.Material = quest.Material.Concat(count.Spec.Material).ToArray();\r
177             if (!_quests.ContainsKey(quest.Id))\r
178                 NeedSave = true;\r
179             SetQuest(quest);\r
180         }\r
181 \r
182         private void SetQuest(QuestStatus quest)\r
183         {\r
184             quest.Count = _countList.GetCount(quest.Id);\r
185             quest.Color = quest.Category <= _color.Length ? _color[quest.Category - 1] : Control.DefaultBackColor;\r
186             _quests[quest.Id] = quest;\r
187         }\r
188 \r
189         public void ClearQuests()\r
190         {\r
191             _quests.Clear();\r
192         }\r
193 \r
194         private void ResetQuests()\r
195         {\r
196             _now = _nowFunc();\r
197             if (!CrossBoundary(LastMorning))\r
198                 return;\r
199             RemoveQuest(QuestInterval.Daily);\r
200             _countList.Remove(QuestInterval.Daily);\r
201             ResetWeekly();\r
202             ResetMonthly();\r
203             ResetQuarterly();\r
204             _lastReset = _now;\r
205             NeedSave = true;\r
206         }\r
207 \r
208         private DateTime LastMorning => _now.Date.AddDays(_now.Hour < 5 ? -1 : 0).AddHours(5);\r
209 \r
210         private void ResetWeekly()\r
211         {\r
212             if (!CrossBoundary(LastMonday.AddHours(5)))\r
213                 return;\r
214             RemoveQuest(QuestInterval.Weekly);\r
215             _countList.Remove(QuestInterval.Weekly);\r
216         }\r
217 \r
218         private DateTime LastMonday => _now.Date.AddDays(-((6 + (int)_now.DayOfWeek) % 7));\r
219 \r
220         private void ResetMonthly()\r
221         {\r
222             if (!CrossBoundary(new DateTime(_now.Year, _now.Month, 1, 5, 0, 0)))\r
223                 return;\r
224             RemoveQuest(QuestInterval.Monthly);\r
225             _countList.Remove(QuestInterval.Monthly);\r
226         }\r
227 \r
228         private void ResetQuarterly()\r
229         {\r
230             if (!CrossBoundary(QuarterlyBoundary.AddHours(5)))\r
231                 return;\r
232             RemoveQuest(QuestInterval.Quarterly);\r
233             _countList.Remove(QuestInterval.Quarterly);\r
234         }\r
235 \r
236         private DateTime QuarterlyBoundary =>\r
237             _now.Month / 3 == 0\r
238                 ? new DateTime(_now.Year - 1, 12, 1)\r
239                 : new DateTime(_now.Year, _now.Month / 3 * 3, 1);\r
240 \r
241         private bool CrossBoundary(DateTime boundary)\r
242         {\r
243             return _lastReset < boundary && boundary <= _now;\r
244         }\r
245 \r
246         private void RemoveQuest(QuestInterval interval)\r
247         {\r
248             foreach (var id in\r
249                 (from kv in _quests\r
250                     where kv.Value.Count.Spec.Interval == interval || // 輸送5と空母3はカウンタを見ないとデイリーにならない\r
251                           kv.Value.Interval == interval\r
252                     select kv.Key).ToArray())\r
253                 _quests.Remove(id);\r
254         }\r
255 \r
256         public void InspectStop(string request)\r
257         {\r
258             var values = HttpUtility.ParseQueryString(request);\r
259             _quests.Remove(int.Parse(values["api_quest_id"]));\r
260             NeedSave = true;\r
261         }\r
262 \r
263         public void InspectClearItemGet(string request)\r
264         {\r
265             var values = HttpUtility.ParseQueryString(request);\r
266             var id = int.Parse(values["api_quest_id"]);\r
267             _countList.Remove(id);\r
268             _quests.Remove(id);\r
269             NeedSave = true;\r
270         }\r
271 \r
272         public bool NeedSave { get; set; }\r
273 \r
274         public void SaveState(Status status)\r
275         {\r
276             NeedSave = false;\r
277             status.QuestLastReset = _lastReset;\r
278             if (_quests != null)\r
279                 status.QuestList = _quests.Values.ToArray();\r
280             if (_countList != null)\r
281                 status.QuestCountList = _countList.NonZeroCountList.ToArray();\r
282         }\r
283 \r
284         public void LoadState(Status status)\r
285         {\r
286             _lastReset = status.QuestLastReset;\r
287             if (status.QuestCountList != null)\r
288                 _countList.SetCountList(status.QuestCountList);\r
289             if (status.QuestList != null)\r
290             {\r
291                 _quests.Clear();\r
292                 foreach (var quest in status.QuestList)\r
293                     SetQuest(quest);\r
294             }\r
295         }\r
296     }\r
297 \r
298 }