OSDN Git Service

前日に未消化のデイリーが消えないバグを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / MainForm.cs
1 // Copyright (C) 2013 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
2 // \r
3 // This program is part of KancolleSniffer.\r
4 //\r
5 // KancolleSniffer is free software: you can redistribute it and/or modify\r
6 // it under the terms of the GNU General Public License as published by\r
7 // the Free Software Foundation, either version 3 of the License, or\r
8 // (at your option) any later version.\r
9 //\r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 //\r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, see <http://www.gnu.org/licenses/>.\r
17 \r
18 using System;\r
19 using System.Collections.Generic;\r
20 using System.Drawing;\r
21 using System.Linq;\r
22 using System.Media;\r
23 using System.Runtime.InteropServices;\r
24 using System.Windows.Forms;\r
25 using System.IO;\r
26 using Codeplex.Data;\r
27 using Fiddler;\r
28 \r
29 namespace KancolleSniffer\r
30 {\r
31     public partial class MainForm : Form\r
32     {\r
33         private readonly Dictionary<int, string> _missions = new Dictionary<int, string>();\r
34         private readonly string[] _missionNames = new string[3];\r
35         private readonly RingTimer[] _missionTimers = new RingTimer[3];\r
36         private readonly RingTimer[] _ndocTimers = new RingTimer[4];\r
37         private readonly int[] _ndocShips = new int[4];\r
38         private readonly RingTimer[] _kdocTimers = new RingTimer[4];\r
39         private int _maxShips;\r
40         private int _nowShips;\r
41         private int _maxItems;\r
42         private int _nowItems;\r
43         private readonly int[] _deckShips = new int[6];\r
44         private readonly Dictionary<int, ShipState> _shipStatuses = new Dictionary<int, ShipState>();\r
45         private readonly Dictionary<int, string> _shipNames = new Dictionary<int, string>();\r
46         private readonly SortedDictionary<int, QuestState> _questList = new SortedDictionary<int, QuestState>();\r
47         private DateTime _questLastUpdated;\r
48         private bool _slotRinged;\r
49 \r
50         private readonly string _shipNamesFile =\r
51             Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "shipnames.json");\r
52 \r
53         private readonly string _missionsFile =\r
54             Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "missions.json");\r
55 \r
56         private struct ShipState\r
57         {\r
58             public int ShipId { get; set; }\r
59             public int Level { get; set; }\r
60             public int ExpToNext { get; set; }\r
61             public int MaxHp { get; set; }\r
62             public int NowHp { get; set; }\r
63         }\r
64 \r
65         private struct QuestState\r
66         {\r
67             public string Name { get; set; }\r
68             public int Progress { get; set; }\r
69         }\r
70 \r
71         public MainForm()\r
72         {\r
73             InitializeComponent();\r
74             FiddlerApplication.AfterSessionComplete += FiddlerApplication_AfterSessionComplete;\r
75             for (var i = 0; i < _missionTimers.Length; i++)\r
76                 _missionTimers[i] = new RingTimer();\r
77             for (var i = 0; i < _ndocTimers.Length; i++)\r
78                 _ndocTimers[i] = new RingTimer();\r
79             for (var i = 0; i < _kdocTimers.Length; i++)\r
80                 _kdocTimers[i] = new RingTimer();\r
81         }\r
82 \r
83         private void FiddlerApplication_AfterSessionComplete(Session oSession)\r
84         {\r
85             if (!oSession.bHasResponse || !oSession.uriContains("/kcsapi/api_"))\r
86                 return;\r
87             var response = oSession.GetResponseBodyAsString();\r
88             if (!response.StartsWith("svdata="))\r
89                 return;\r
90             response = response.Remove(0, "svdata=".Length);\r
91             var json = DynamicJson.Parse(response);\r
92             if (!json.IsDefined("api_data"))\r
93                 return;\r
94             json = json.api_data;\r
95             if (oSession.url.EndsWith("api_get_member/ship"))\r
96             {\r
97                 ParseShipData(json);\r
98             }\r
99             else if (oSession.uriContains("api_get_master/mission"))\r
100             {\r
101                 ParseMission(json);\r
102             }\r
103             else if (oSession.uriContains("api_get_member/ndock"))\r
104             {\r
105                 ParseNDock(json);\r
106                 Invoke(new Action(UpdateTimers));\r
107             }\r
108             else if (oSession.uriContains("api_get_member/kdock"))\r
109             {\r
110                 ParseKDock(json);\r
111                 Invoke(new Action(UpdateTimers));\r
112             }\r
113             else if (oSession.url.Contains("api_get_member/deck"))\r
114             {\r
115                 ParseDeck(json);\r
116                 Invoke(new Action(UpdateShipInfo));\r
117                 Invoke(new Action(UpdateMissionLabels));\r
118                 Invoke(new Action(UpdateTimers));\r
119             }\r
120             else if (oSession.uriContains("api_get_member/basic"))\r
121             {\r
122                 _maxShips = (int)json.api_max_chara;\r
123                 _maxItems = (int)json.api_max_slotitem;\r
124                 Invoke(new Action(UpdateSlotCount));\r
125             }\r
126             else if (oSession.uriContains("api_get_member/record"))\r
127             {\r
128                 _nowShips = (int)json.api_ship[0];\r
129                 _maxShips = (int)json.api_ship[1];\r
130                 _nowItems = (int)json.api_slotitem[0];\r
131                 _maxItems = (int)json.api_slotitem[1];\r
132                 Invoke(new Action(UpdateSlotCount));\r
133             }\r
134             else if (oSession.uriContains("api_get_member/material"))\r
135             {\r
136                 foreach (var entry in json)\r
137                 {\r
138                     if ((int)entry.api_id != 6)\r
139                         continue;\r
140                     var backet = ((int)entry.api_value).ToString("D");\r
141                     Invoke(new Action<string>(text => labelNumOfBuckets.Text = text), backet);\r
142                     break;\r
143                 }\r
144             }\r
145             else if (oSession.uriContains("api_get_member/slotitem"))\r
146             {\r
147                 ParseSlotItem(json);\r
148                 Invoke(new Action(UpdateSlotCount));\r
149             }\r
150             else if (oSession.uriContains("api_get_member/ship2"))\r
151             {\r
152                 ParseShipStatus(json);\r
153                 Invoke(new Action(UpdateShipInfo));\r
154             }\r
155             else if (oSession.uriContains("api_get_member/ship3"))\r
156             {\r
157                 var deck = json.api_deck_data;\r
158                 foreach (var entry in deck)\r
159                 {\r
160                     if ((int)entry.api_id != 1)\r
161                         continue;\r
162                     for (var i = 0; i < 6; i++)\r
163                         _deckShips[i] = (int)entry.api_ship[i];\r
164                 }\r
165                 var ship = json.api_ship_data;\r
166                 ParseShipStatus(ship);\r
167                 Invoke(new Action(UpdateShipInfo));\r
168             }\r
169             else if (oSession.uriContains("api_req_sortie/battleresult"))\r
170             {\r
171                 if (!json.IsDefined("api_get_ship"))\r
172                     return;\r
173                 var entry = json.api_get_ship;\r
174                 _shipNames[(int)entry.api_ship_id] = (string)entry.api_ship_name;\r
175             }\r
176             else if (oSession.uriContains("api_get_member/questlist"))\r
177             {\r
178                 ParseQuestList(json);\r
179                 Invoke(new Action(UpdateQuestList));\r
180             }\r
181         }\r
182 \r
183         private void Form1_Load(object sender, EventArgs e)\r
184         {\r
185             LoadNames();\r
186             FiddlerApplication.Startup(0, FiddlerCoreStartupFlags.RegisterAsSystemProxy);\r
187         }\r
188 \r
189         private void Form1_FormClosing(object sender, FormClosingEventArgs e)\r
190         {\r
191             FiddlerApplication.Shutdown();\r
192             SaveNames();\r
193         }\r
194 \r
195         private void timerMain_Tick(object sender, EventArgs e)\r
196         {\r
197             UpdateTimers();\r
198         }\r
199 \r
200         private void ParseMission(dynamic json)\r
201         {\r
202             foreach (var entry in json)\r
203                 _missions[(int)entry.api_id] = (string)entry.api_name;\r
204         }\r
205 \r
206         private void ParseShipData(dynamic json)\r
207         {\r
208             foreach (var entry in json)\r
209                 _shipNames[(int)entry.api_ship_id] = (string)entry.api_name;\r
210         }\r
211 \r
212         private void LoadNames()\r
213         {\r
214             try\r
215             {\r
216                 ParseMission(DynamicJson.Parse(File.ReadAllText(_missionsFile)));\r
217             }\r
218             catch (FileNotFoundException)\r
219             {\r
220             }\r
221             try\r
222             {\r
223                 ParseShipData(DynamicJson.Parse(File.ReadAllText(_shipNamesFile)));\r
224             }\r
225             catch (FileNotFoundException)\r
226             {\r
227             }\r
228         }\r
229 \r
230         private void SaveNames()\r
231         {\r
232             var ship = from data in _shipNames select new {api_ship_id = data.Key, api_name = data.Value};\r
233             File.WriteAllText(_shipNamesFile, DynamicJson.Serialize(ship));\r
234 \r
235             var mission = from data in _missions select new {api_id = data.Key, api_name = data.Value};\r
236             File.WriteAllText(_missionsFile, DynamicJson.Serialize(mission));\r
237         }\r
238 \r
239         private void ParseNDock(dynamic json)\r
240         {\r
241             foreach (var entry in json)\r
242             {\r
243                 var id = (int)entry.api_id;\r
244                 _ndocTimers[id - 1].EndTime = (double)entry.api_complete_time;\r
245                 _ndocShips[id - 1] = (int)entry.api_ship_id;\r
246             }\r
247             Invoke(new Action(UpdateNDocLabels));\r
248         }\r
249 \r
250         private void ParseKDock(dynamic json)\r
251         {\r
252             foreach (var entry in json)\r
253             {\r
254                 var id = (int)entry.api_id;\r
255                 _kdocTimers[id - 1].EndTime = (double)entry.api_complete_time;\r
256             }\r
257         }\r
258 \r
259         private void ParseDeck(dynamic json)\r
260         {\r
261             foreach (var entry in json)\r
262             {\r
263                 var id = (int)entry.api_id;\r
264                 if (id == 1)\r
265                 {\r
266                     Invoke((Action<string>)(text => labelFleet1.Text = text), (string)entry.api_name);\r
267                     for (var i = 0; i < 6; i++)\r
268                         _deckShips[i] = (int)entry.api_ship[i];\r
269                     continue;\r
270                 }\r
271                 id -= 2;\r
272                 var mission = entry.api_mission;\r
273                 if (mission[0] == 0)\r
274                 {\r
275                     _missionNames[id] = "";\r
276                     _missionTimers[id].EndTime = 0;\r
277                     continue;\r
278                 }\r
279                 string name;\r
280                 _missionNames[id] = _missions.TryGetValue((int)mission[1], out name) ? name : "不明";\r
281                 _missionTimers[id].EndTime = mission[2];\r
282             }\r
283         }\r
284 \r
285         private void ParseSlotItem(dynamic json)\r
286         {\r
287             _nowItems = ((object[])json).Count();\r
288         }\r
289 \r
290         private void ParseShipStatus(dynamic json)\r
291         {\r
292             _shipStatuses.Clear();\r
293             foreach (var entry in json)\r
294             {\r
295                 var data = new ShipState\r
296                 {\r
297                     ShipId = (int)entry.api_ship_id,\r
298                     Level = (int)entry.api_lv,\r
299                     ExpToNext = (int)entry.api_exp[1],\r
300                     MaxHp = (int)entry.api_maxhp,\r
301                     NowHp = (int)entry.api_nowhp\r
302                 };\r
303                 _shipStatuses[(int)entry.api_id] = data;\r
304             }\r
305             _nowShips = _shipStatuses.Count;\r
306         }\r
307 \r
308         private void ParseQuestList(dynamic json)\r
309         {\r
310             var resetTime = DateTime.Today.AddHours(5);\r
311             if (DateTime.Now >= resetTime && _questLastUpdated < resetTime)\r
312             {\r
313                 // 前日に未消化のデイリーを消す。\r
314                 _questList.Clear();\r
315                 _questLastUpdated = DateTime.Now;\r
316             }\r
317             foreach (var entry in json.api_list)\r
318             {\r
319                 if (entry is double)\r
320                     continue;\r
321                 var id = (int)entry.api_no;\r
322                 var state = (int)entry.api_state;\r
323                 var progress = (int)entry.api_progress_flag;\r
324                 var name = (string)entry.api_title;\r
325 \r
326                 switch (progress)\r
327                 {\r
328                     case 0:\r
329                         break;\r
330                     case 1:\r
331                         progress = 50;\r
332                         break;\r
333                     case 2:\r
334                         progress = 80;\r
335                         break;\r
336                 }\r
337                 switch (state)\r
338                 {\r
339                     case 2:\r
340                         _questList[id] = new QuestState {Name = name, Progress = progress};\r
341                         break;\r
342                     case 1:\r
343                     case 3:\r
344                         _questList.Remove(id);\r
345                         continue;\r
346                 }\r
347             }\r
348         }\r
349 \r
350         private void UpdateSlotCount()\r
351         {\r
352             labelNumOfShips.Text = string.Format("{0:D}/{1:D}", _nowShips, _maxShips);\r
353             if (_maxShips == 0 || // recordよりship3の方が先なので0の場合がある。\r
354                 _nowShips < _maxShips - 4)\r
355             {\r
356                 labelNumOfShips.ForeColor = Color.Black;\r
357                 _slotRinged = false;\r
358             }\r
359             else\r
360             {\r
361                 labelNumOfShips.ForeColor = Color.Red;\r
362                 if (!_slotRinged)\r
363                 {\r
364                     Ring();\r
365                     _slotRinged = true;\r
366                 }\r
367             }\r
368             labelNumOfEquips.Text = string.Format("{0:D}/{1:D}", _nowItems, _maxItems);\r
369         }\r
370 \r
371         private void UpdateMissionLabels()\r
372         {\r
373             var labels = new[] {labelMissionName1, labelMissionName2, labelMissionName3};\r
374             for (var i = 0; i < 3; i++)\r
375                 labels[i].Text = _missionNames[i];\r
376         }\r
377 \r
378         private void UpdateNDocLabels()\r
379         {\r
380             var ship = new[] {labelRepairShip1, labelRepairShip2, labelRepairShip3, labelRepairShip4};\r
381             var i = 0;\r
382             foreach (var id in _ndocShips)\r
383             {\r
384                 ShipState shipStatus;\r
385                 string text;\r
386                 ship[i++].Text = id == 0\r
387                     ? ""\r
388                     : _shipStatuses.TryGetValue(id, out shipStatus) &&\r
389                       _shipNames.TryGetValue(shipStatus.ShipId, out text)\r
390                         ? text\r
391                         : "不明";\r
392             }\r
393         }\r
394 \r
395         private void UpdateShipInfo()\r
396         {\r
397             var name = new[] {labelShip1, labelShip2, labelShip3, labelShip4, labelShip5, labelShip6};\r
398             var lv = new[] {labelLv1, labelLv2, labelLv3, labelLv4, labelLv5, labelLv6};\r
399             var hp = new[] {labelHP1, labelHP2, labelHP3, labelHP4, labelHP5, labelHP6};\r
400             var next = new[] {labelNextLv1, labelNextLv2, labelNextLv3, labelNextLv4, labelNextLv5, labelNextLv6};\r
401 \r
402             if (_shipStatuses.Count == 0)\r
403                 return;\r
404             for (var i = 0; i < 6; i++)\r
405             {\r
406                 var id = _deckShips[i];\r
407                 ShipState info;\r
408                 if (id == -1 || id == 0 || !_shipStatuses.TryGetValue(id, out info))\r
409                 {\r
410                     name[i].Text = "";\r
411                     lv[i].Text = "0";\r
412                     hp[i].Text = "0/0";\r
413                     hp[i].BackColor = DefaultBackColor;\r
414                     next[i].Text = "0";\r
415                     continue;\r
416                 }\r
417                 string text;\r
418                 name[i].Text = _shipNames.TryGetValue(info.ShipId, out text) ? text : "不明";\r
419                 lv[i].Text = info.Level.ToString("D");\r
420                 hp[i].Text = string.Format("{0:D}/{1:D}", info.NowHp, info.MaxHp);\r
421                 var damage = (double)info.NowHp / info.MaxHp;\r
422                 hp[i].BackColor = damage > 0.5 ? DefaultBackColor : damage > 0.25 ? Color.Orange : Color.Red;\r
423                 next[i].Text = info.ExpToNext.ToString("D");\r
424             }\r
425             UpdateSlotCount();\r
426         }\r
427 \r
428         private void UpdateTimers()\r
429         {\r
430             var mission = new[] {labelMission1, labelMission2, labelMission3};\r
431             var i = 0;\r
432             foreach (var timer in _missionTimers)\r
433             {\r
434                 timer.Update();\r
435                 SetTimerLabel(timer, mission[i++]);\r
436                 if (!timer.NeedRing)\r
437                     continue;\r
438                 Ring();\r
439                 timer.NeedRing = false;\r
440             }\r
441             var ndock = new[] {labelRepair1, labelRepair2, labelRepair3, labelRepair4};\r
442             i = 0;\r
443             foreach (var timer in _ndocTimers)\r
444             {\r
445                 timer.Update();\r
446                 SetTimerLabel(timer, ndock[i++]);\r
447                 if (!timer.NeedRing)\r
448                     continue;\r
449                 Ring();\r
450                 timer.NeedRing = false;\r
451             }\r
452             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
453             i = 0;\r
454             foreach (var timer in _kdocTimers)\r
455             {\r
456                 timer.Update();\r
457                 SetTimerLabel(timer, kdock[i++]);\r
458                 if (!timer.NeedRing)\r
459                     continue;\r
460                 Ring();\r
461                 timer.NeedRing = false;\r
462             }\r
463         }\r
464 \r
465         private void SetTimerLabel(RingTimer timer, Label label)\r
466         {\r
467             if (timer.NeedRing)\r
468                 label.ForeColor = Color.Red;\r
469             if (!timer.IsSet)\r
470                 label.ForeColor = Color.Black;\r
471             label.Text = timer.ToString();\r
472         }\r
473 \r
474         private void UpdateQuestList()\r
475         {\r
476             var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5};\r
477             var progress = new[] {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5};\r
478             var i = 0;\r
479             foreach (var quest in _questList.Values)\r
480             {\r
481                 if (i == 5)\r
482                     break;\r
483                 name[i].Text = quest.Name;\r
484                 progress[i++].Text = string.Format("{0:D}%", quest.Progress);\r
485             }\r
486             for (; i < 5; i++)\r
487             {\r
488                 name[i].Text = "";\r
489                 progress[i].Text = "";\r
490             }\r
491         }\r
492 \r
493         private void Ring()\r
494         {\r
495             SystemSounds.Asterisk.Play();\r
496             var info = new FLASHWINFO();\r
497             info.cbSize = Convert.ToUInt32(Marshal.SizeOf(info));\r
498             info.hwnd = Handle;\r
499             info.dwFlags = 3; // FLASHW_ALL\r
500             info.uCount = 3;\r
501             info.dwTimeout = 0;\r
502             FlashWindowEx(ref info);\r
503         }\r
504 \r
505         [StructLayout(LayoutKind.Sequential)]\r
506         private struct FLASHWINFO\r
507         {\r
508             public UInt32 cbSize;\r
509             public IntPtr hwnd;\r
510             public UInt32 dwFlags;\r
511             public UInt32 uCount;\r
512             public UInt32 dwTimeout;\r
513         }\r
514 \r
515         [DllImport("user32.dll")]\r
516         private static extern Int32 FlashWindowEx(ref FLASHWINFO pwfi);\r
517 \r
518         private class RingTimer\r
519         {\r
520             private bool _ringed;\r
521             private DateTime _endTime;\r
522             private TimeSpan _rest;\r
523 \r
524             public double EndTime\r
525             {\r
526                 set\r
527                 {\r
528 // ReSharper disable once CompareOfFloatsByEqualityOperator\r
529                     if (value != 0)\r
530                         _endTime = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(value / 1000);\r
531                     else\r
532                     {\r
533                         _endTime = DateTime.MinValue;\r
534                         _ringed = false;\r
535                     }\r
536                 }\r
537             }\r
538 \r
539             public void Update()\r
540             {\r
541                 if (_endTime == DateTime.MinValue)\r
542                 {\r
543                     _rest = TimeSpan.Zero;\r
544                     return;\r
545                 }\r
546                 _rest = _endTime - DateTime.Now;\r
547                 if (_rest < TimeSpan.Zero)\r
548                     _rest = TimeSpan.Zero;\r
549                 if (_rest >= TimeSpan.FromMinutes(1) || _ringed)\r
550                     return;\r
551                 _ringed = true;\r
552                 NeedRing = true;\r
553             }\r
554 \r
555             public bool NeedRing { get; set; }\r
556 \r
557             public bool IsSet\r
558             {\r
559                 get { return _endTime != DateTime.MinValue; }\r
560             }\r
561 \r
562             public override string ToString()\r
563             {\r
564                 return _rest.Days == 0 ? _rest.ToString(@"hh\:mm\:ss") : _rest.ToString(@"d\.hh\:mm");\r
565             }\r
566         }\r
567     }\r
568 }