OSDN Git Service

モデルとビューの分離
authorKazuhiro Fujieda <fujieda@users.sourceforge.jp>
Mon, 30 Dec 2013 16:20:04 +0000 (01:20 +0900)
committerKazuhiro Fujieda <fujieda@users.sourceforge.jp>
Sat, 4 Jan 2014 03:59:26 +0000 (12:59 +0900)
KancolleSniffer/ItemInfo.cs [new file with mode: 0644]
KancolleSniffer/KancolleSniffer.csproj
KancolleSniffer/MainForm.cs
KancolleSniffer/MissionInfo.cs [new file with mode: 0644]
KancolleSniffer/QuestInfo.cs [new file with mode: 0644]
KancolleSniffer/ShipInfo.cs [new file with mode: 0644]
KancolleSniffer/Sniffer.cs [new file with mode: 0644]

diff --git a/KancolleSniffer/ItemInfo.cs b/KancolleSniffer/ItemInfo.cs
new file mode 100644 (file)
index 0000000..ac3cfd4
--- /dev/null
@@ -0,0 +1,83 @@
+// Copyright (C) 2013 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// \r
+// This program is part of KancolleSniffer.\r
+//\r
+// KancolleSniffer is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+\r
+namespace KancolleSniffer\r
+{\r
+    public class ItemInfo\r
+    {\r
+        private int _nowShips;\r
+\r
+        public int NowShips\r
+        {\r
+            get { return _nowShips; }\r
+            set\r
+            {\r
+                if (MaxShips != 0)\r
+                {\r
+                    var limit = MaxShips - MarginShips;\r
+                    NeedRing = _nowShips < limit && value >= limit;\r
+                }\r
+                _nowShips = value;\r
+            }\r
+        }\r
+\r
+        public int MaxShips { get; set; }\r
+        public int MarginShips { get; set; }\r
+        public bool NeedRing { get; set; }\r
+        public int NowItems { get; set; }\r
+        public int MaxItems { get; set; }\r
+        public int NumBuckets { get; set; }\r
+\r
+        public bool TooManyShips\r
+        {\r
+            get { return MaxShips != 0 && NowShips >= MaxShips - MarginShips; }\r
+        }\r
+\r
+        public ItemInfo()\r
+        {\r
+            MarginShips = 4;\r
+        }\r
+\r
+        public void InspectBasic(dynamic json)\r
+        {\r
+            MaxShips = (int)json.api_max_chara;\r
+            MaxItems = (int)json.api_max_slotitem;\r
+        }\r
+        public void InspectRecord(dynamic json)\r
+        {\r
+            NowShips = (int)json.api_ship[0];\r
+            MaxShips = (int)json.api_ship[1];\r
+            NowItems = (int)json.api_slotitem[0];\r
+            MaxItems = (int)json.api_slotitem[1];\r
+        }\r
+\r
+        public void InspectMaterial(dynamic json)\r
+        {\r
+            foreach (var entry in json)\r
+            {\r
+                if ((int)entry.api_id != 6)\r
+                    continue;\r
+                NumBuckets = (int)entry.api_value;\r
+            }\r
+        }\r
+\r
+        public void InspectSlotItem(dynamic json)\r
+        {\r
+            NowItems = ((object[])json).Length;\r
+        }\r
+    }\r
+}
\ No newline at end of file
index 07b8052..858ea7d 100644 (file)
@@ -23,6 +23,8 @@
     <ErrorReport>prompt</ErrorReport>\r
     <WarningLevel>4</WarningLevel>\r
     <Prefer32Bit>false</Prefer32Bit>\r
+    <DocumentationFile>\r
+    </DocumentationFile>\r
   </PropertyGroup>\r
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">\r
     <PlatformTarget>AnyCPU</PlatformTarget>\r
     <Compile Include="MainForm.Designer.cs">\r
       <DependentUpon>MainForm.cs</DependentUpon>\r
     </Compile>\r
+    <Compile Include="MissionInfo.cs" />\r
     <Compile Include="Program.cs" />\r
     <Compile Include="Properties\AssemblyInfo.cs" />\r
+    <Compile Include="QuestInfo.cs" />\r
+    <Compile Include="ShipInfo.cs" />\r
+    <Compile Include="Sniffer.cs" />\r
+    <Compile Include="ItemInfo.cs" />\r
     <EmbeddedResource Include="MainForm.resx">\r
       <DependentUpon>MainForm.cs</DependentUpon>\r
     </EmbeddedResource>\r
index ecc7dbf..8fb35a5 100644 (file)
 // along with this program; if not, see <http://www.gnu.org/licenses/>.\r
 \r
 using System;\r
-using System.Collections.Generic;\r
 using System.Drawing;\r
 using System.Linq;\r
 using System.Media;\r
 using System.Runtime.InteropServices;\r
 using System.Windows.Forms;\r
-using System.IO;\r
 using Codeplex.Data;\r
 using Fiddler;\r
 \r
 namespace KancolleSniffer\r
 {\r
+    /// <summary>\r
+    /// メインフォーム\r
+    /// </summary>\r
     public partial class MainForm : Form\r
     {\r
-        private readonly Dictionary<int, string> _missions = new Dictionary<int, string>();\r
-        private readonly string[] _missionNames = new string[3];\r
-        private readonly RingTimer[] _missionTimers = new RingTimer[3];\r
-        private readonly RingTimer[] _ndocTimers = new RingTimer[4];\r
-        private readonly int[] _ndocShips = new int[4];\r
-        private readonly RingTimer[] _kdocTimers = new RingTimer[4];\r
-        private int _maxShips;\r
-        private int _nowShips;\r
-        private int _maxItems;\r
-        private int _nowItems;\r
-        private readonly int[] _deckShips = new int[6];\r
-        private readonly Dictionary<int, ShipState> _shipStatuses = new Dictionary<int, ShipState>();\r
-        private readonly Dictionary<int, string> _shipNames = new Dictionary<int, string>();\r
-        private readonly SortedDictionary<int, QuestState> _questList = new SortedDictionary<int, QuestState>();\r
-        private DateTime _questLastUpdated;\r
-        private bool _slotRinged;\r
-        private bool _updateCond;\r
-        private DateTime[] _condEndTime = new DateTime[3];\r
-\r
-        private readonly string _shipNamesFile =\r
-            Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "shipnames.json");\r
-\r
-        private readonly string _missionsFile =\r
-            Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "missions.json");\r
-\r
-        private struct ShipState\r
-        {\r
-            public int ShipId { get; set; }\r
-            public int Level { get; set; }\r
-            public int ExpToNext { get; set; }\r
-            public int MaxHp { get; set; }\r
-            public int NowHp { get; set; }\r
-            public int Cond { get; set; }\r
-        }\r
-\r
-        private struct QuestState\r
-        {\r
-            public string Name { get; set; }\r
-            public int Progress { get; set; }\r
-        }\r
+        private readonly Sniffer _sniffer = new Sniffer();\r
 \r
         public MainForm()\r
         {\r
             InitializeComponent();\r
             FiddlerApplication.AfterSessionComplete += FiddlerApplication_AfterSessionComplete;\r
-            for (var i = 0; i < _missionTimers.Length; i++)\r
-                _missionTimers[i] = new RingTimer();\r
-            for (var i = 0; i < _ndocTimers.Length; i++)\r
-                _ndocTimers[i] = new RingTimer();\r
-            for (var i = 0; i < _kdocTimers.Length; i++)\r
-                _kdocTimers[i] = new RingTimer();\r
         }\r
 \r
         private void FiddlerApplication_AfterSessionComplete(Session oSession)\r
@@ -95,98 +51,29 @@ namespace KancolleSniffer
             if (!json.IsDefined("api_data"))\r
                 return;\r
             json = json.api_data;\r
-            if (oSession.url.EndsWith("api_get_member/ship"))\r
-            {\r
-                ParseShipData(json);\r
-            }\r
-            else if (oSession.uriContains("api_get_master/mission"))\r
-            {\r
-                ParseMission(json);\r
-            }\r
-            else if (oSession.uriContains("api_get_member/ndock"))\r
-            {\r
-                ParseNDock(json);\r
-                Invoke(new Action(UpdateTimers));\r
-            }\r
-            else if (oSession.uriContains("api_get_member/kdock"))\r
-            {\r
-                ParseKDock(json);\r
-                Invoke(new Action(UpdateTimers));\r
-            }\r
-            else if (oSession.uriContains("api_get_member/deck"))\r
-            {\r
-                if (!oSession.uriContains("deck_port"))\r
-                    _updateCond = true;\r
-                ParseDeck(json);\r
-                Invoke(new Action(UpdateShipInfo));\r
+            UpdateInfo update = _sniffer.Sniff(oSession.url, json);\r
+            if ((update & UpdateInfo.Item) != 0)\r
+                Invoke(new Action(UpdateItemInfo));\r
+            if ((update & UpdateInfo.Mission) != 0)\r
                 Invoke(new Action(UpdateMissionLabels));\r
-                Invoke(new Action(UpdateTimers));\r
-            }\r
-            else if (oSession.uriContains("api_get_member/basic"))\r
-            {\r
-                _maxShips = (int)json.api_max_chara;\r
-                _maxItems = (int)json.api_max_slotitem;\r
-                Invoke(new Action(UpdateSlotCount));\r
-            }\r
-            else if (oSession.uriContains("api_get_member/record"))\r
-            {\r
-                _nowShips = (int)json.api_ship[0];\r
-                _maxShips = (int)json.api_ship[1];\r
-                _nowItems = (int)json.api_slotitem[0];\r
-                _maxItems = (int)json.api_slotitem[1];\r
-                Invoke(new Action(UpdateSlotCount));\r
-            }\r
-            else if (oSession.uriContains("api_get_member/material"))\r
-            {\r
-                foreach (var entry in json)\r
-                {\r
-                    if ((int)entry.api_id != 6)\r
-                        continue;\r
-                    var backet = ((int)entry.api_value).ToString("D");\r
-                    Invoke(new Action<string>(text => labelNumOfBuckets.Text = text), backet);\r
-                    break;\r
-                }\r
-            }\r
-            else if (oSession.uriContains("api_get_member/slotitem"))\r
-            {\r
-                ParseSlotItem(json);\r
-                Invoke(new Action(UpdateSlotCount));\r
-            }\r
-            else if (oSession.uriContains("api_get_member/ship2"))\r
-            {\r
-                ParseShipStatus(json);\r
-                Invoke(new Action(UpdateShipInfo));\r
-            }\r
-            else if (oSession.uriContains("api_get_member/ship3"))\r
-            {\r
-                var ship = json.api_ship_data;\r
-                ParseShipStatus(ship);\r
+            if ((update & UpdateInfo.NDock) != 0)\r
+                Invoke(new Action(UpdateNDocLabels));\r
+            if ((update & UpdateInfo.Ship) != 0)\r
                 Invoke(new Action(UpdateShipInfo));\r
-            }\r
-            else if (oSession.uriContains("api_req_sortie/battleresult"))\r
-            {\r
-                if (!json.IsDefined("api_get_ship"))\r
-                    return;\r
-                var entry = json.api_get_ship;\r
-                _shipNames[(int)entry.api_ship_id] = (string)entry.api_ship_name;\r
-            }\r
-            else if (oSession.uriContains("api_get_member/questlist"))\r
-            {\r
-                ParseQuestList(json);\r
+            if ((update & UpdateInfo.Quest) != 0)\r
                 Invoke(new Action(UpdateQuestList));\r
-            }\r
         }\r
 \r
         private void Form1_Load(object sender, EventArgs e)\r
         {\r
-            LoadNames();\r
+            _sniffer.LoadNames();\r
             FiddlerApplication.Startup(0, FiddlerCoreStartupFlags.RegisterAsSystemProxy);\r
         }\r
 \r
         private void Form1_FormClosing(object sender, FormClosingEventArgs e)\r
         {\r
             FiddlerApplication.Shutdown();\r
-            SaveNames();\r
+            _sniffer.SaveNames();\r
         }\r
 \r
         private void timerMain_Tick(object sender, EventArgs e)\r
@@ -194,231 +81,32 @@ namespace KancolleSniffer
             UpdateTimers();\r
         }\r
 \r
-        private void ParseMission(dynamic json)\r
-        {\r
-            foreach (var entry in json)\r
-                _missions[(int)entry.api_id] = (string)entry.api_name;\r
-        }\r
-\r
-        private void ParseShipData(dynamic json)\r
-        {\r
-            foreach (var entry in json)\r
-                _shipNames[(int)entry.api_ship_id] = (string)entry.api_name;\r
-        }\r
-\r
-        private void LoadNames()\r
-        {\r
-            try\r
-            {\r
-                ParseMission(DynamicJson.Parse(File.ReadAllText(_missionsFile)));\r
-            }\r
-            catch (FileNotFoundException)\r
-            {\r
-            }\r
-            try\r
-            {\r
-                ParseShipData(DynamicJson.Parse(File.ReadAllText(_shipNamesFile)));\r
-            }\r
-            catch (FileNotFoundException)\r
-            {\r
-            }\r
-        }\r
-\r
-        private void SaveNames()\r
-        {\r
-            var ship = from data in _shipNames select new {api_ship_id = data.Key, api_name = data.Value};\r
-            File.WriteAllText(_shipNamesFile, DynamicJson.Serialize(ship));\r
-\r
-            var mission = from data in _missions select new {api_id = data.Key, api_name = data.Value};\r
-            File.WriteAllText(_missionsFile, DynamicJson.Serialize(mission));\r
-        }\r
-\r
-        private void ParseNDock(dynamic json)\r
-        {\r
-            foreach (var entry in json)\r
-            {\r
-                var id = (int)entry.api_id;\r
-                _ndocTimers[id - 1].EndTime = (double)entry.api_complete_time;\r
-                _ndocShips[id - 1] = (int)entry.api_ship_id;\r
-            }\r
-            Invoke(new Action(UpdateNDocLabels));\r
-        }\r
-\r
-        private void ParseKDock(dynamic json)\r
+        private void UpdateItemInfo()\r
         {\r
-            foreach (var entry in json)\r
+            var item = _sniffer.Item;\r
+            labelNumOfShips.Text = string.Format("{0:D}/{1:D}", item.NowShips, item.MaxShips);\r
+            labelNumOfShips.ForeColor = item.TooManyShips ? Color.Red : Color.Black;\r
+            if (item.NeedRing)\r
             {\r
-                var id = (int)entry.api_id;\r
-                _kdocTimers[id - 1].EndTime = (double)entry.api_complete_time;\r
-            }\r
-        }\r
-\r
-        private void ParseDeck(dynamic json)\r
-        {\r
-            foreach (var entry in json)\r
-            {\r
-                var id = (int)entry.api_id;\r
-                if (id == 1)\r
-                {\r
-                    Invoke((Action<string>)(text => labelFleet1.Text = text), (string)entry.api_name);\r
-                    for (var i = 0; i < _deckShips.Count(); i++)\r
-                    {\r
-                        var ship = (int)entry.api_ship[i];\r
-                        if (_deckShips[i] != ship)\r
-                            _updateCond = true;\r
-                        _deckShips[i] = ship;\r
-                    }\r
-                    continue;\r
-                }\r
-                id -= 2;\r
-                var mission = entry.api_mission;\r
-                if (mission[0] == 0)\r
-                {\r
-                    _missionNames[id] = "";\r
-                    _missionTimers[id].EndTime = 0;\r
-                    continue;\r
-                }\r
-                string name;\r
-                _missionNames[id] = _missions.TryGetValue((int)mission[1], out name) ? name : "不明";\r
-                _missionTimers[id].EndTime = mission[2];\r
-            }\r
-        }\r
-\r
-        private void ParseSlotItem(dynamic json)\r
-        {\r
-            _nowItems = ((object[])json).Count();\r
-        }\r
-\r
-        private void ParseShipStatus(dynamic json)\r
-        {\r
-            _shipStatuses.Clear();\r
-            foreach (var entry in json)\r
-            {\r
-                var data = new ShipState\r
-                {\r
-                    ShipId = (int)entry.api_ship_id,\r
-                    Level = (int)entry.api_lv,\r
-                    ExpToNext = (int)entry.api_exp[1],\r
-                    MaxHp = (int)entry.api_maxhp,\r
-                    NowHp = (int)entry.api_nowhp,\r
-                    Cond = (int)entry.api_cond\r
-                };\r
-                _shipStatuses[(int)entry.api_id] = data;\r
-            }\r
-            _nowShips = _shipStatuses.Count;\r
-            if (!_updateCond)\r
-                return;\r
-            _updateCond = false;\r
-            var cond = int.MaxValue;\r
-            foreach (var id in _deckShips)\r
-            {\r
-                ShipState info;\r
-                if (id == -1 || id == 0 || !_shipStatuses.TryGetValue(id, out info))\r
-                    continue;\r
-                if (info.Cond < cond)\r
-                    cond = info.Cond;\r
-            }\r
-            if (cond != int.MaxValue)\r
-                SetCondTimers(cond);\r
-        }\r
-\r
-        private void SetCondTimers(int cond)\r
-        {\r
-            _condEndTime[0] = CondTimerEndTime(cond, 30);\r
-            _condEndTime[1] = CondTimerEndTime(cond, 40);\r
-            _condEndTime[2] = CondTimerEndTime(cond, 49);\r
-        }\r
-\r
-        private DateTime CondTimerEndTime(int cond, int thresh)\r
-        {\r
-            return (cond < thresh) ? DateTime.Now.AddMinutes((thresh - cond + 2) / 3 * 3) : DateTime.MinValue;\r
-        }\r
-\r
-        private void ParseQuestList(dynamic json)\r
-        {\r
-            var resetTime = DateTime.Today.AddHours(5);\r
-            if (DateTime.Now >= resetTime && _questLastUpdated < resetTime)\r
-            {\r
-                // 前日に未消化のデイリーを消す。\r
-                _questList.Clear();\r
-                _questLastUpdated = DateTime.Now;\r
-            }\r
-            foreach (var entry in json.api_list)\r
-            {\r
-                if (entry is double)\r
-                    continue;\r
-                var id = (int)entry.api_no;\r
-                var state = (int)entry.api_state;\r
-                var progress = (int)entry.api_progress_flag;\r
-                var name = (string)entry.api_title;\r
-\r
-                switch (progress)\r
-                {\r
-                    case 0:\r
-                        break;\r
-                    case 1:\r
-                        progress = 50;\r
-                        break;\r
-                    case 2:\r
-                        progress = 80;\r
-                        break;\r
-                }\r
-                switch (state)\r
-                {\r
-                    case 2:\r
-                        _questList[id] = new QuestState {Name = name, Progress = progress};\r
-                        break;\r
-                    case 1:\r
-                    case 3:\r
-                        _questList.Remove(id);\r
-                        continue;\r
-                }\r
-            }\r
-        }\r
-\r
-        private void UpdateSlotCount()\r
-        {\r
-            labelNumOfShips.Text = string.Format("{0:D}/{1:D}", _nowShips, _maxShips);\r
-            if (_maxShips == 0 || // recordよりship3の方が先なので0の場合がある。\r
-                _nowShips < _maxShips - 4)\r
-            {\r
-                labelNumOfShips.ForeColor = Color.Black;\r
-                _slotRinged = false;\r
-            }\r
-            else\r
-            {\r
-                labelNumOfShips.ForeColor = Color.Red;\r
-                if (!_slotRinged)\r
-                {\r
-                    Ring();\r
-                    _slotRinged = true;\r
-                }\r
+                Ring();\r
+                item.NeedRing = false;\r
             }\r
-            labelNumOfEquips.Text = string.Format("{0:D}/{1:D}", _nowItems, _maxItems);\r
+            labelNumOfEquips.Text = string.Format("{0:D}/{1:D}", item.NowItems, item.MaxItems);\r
+            labelNumOfBuckets.Text = item.NumBuckets.ToString("D");\r
         }\r
 \r
         private void UpdateMissionLabels()\r
         {\r
             var labels = new[] {labelMissionName1, labelMissionName2, labelMissionName3};\r
-            for (var i = 0; i < 3; i++)\r
-                labels[i].Text = _missionNames[i];\r
+            for (var i = 0; i < labels.Length; i++)\r
+                labels[i].Text = _sniffer.Missions[i].Name;\r
         }\r
 \r
         private void UpdateNDocLabels()\r
         {\r
             var ship = new[] {labelRepairShip1, labelRepairShip2, labelRepairShip3, labelRepairShip4};\r
-            var i = 0;\r
-            foreach (var id in _ndocShips)\r
-            {\r
-                ShipState shipStatus;\r
-                string text;\r
-                ship[i++].Text = id == 0\r
-                    ? ""\r
-                    : _shipStatuses.TryGetValue(id, out shipStatus) &&\r
-                      _shipNames.TryGetValue(shipStatus.ShipId, out text)\r
-                        ? text\r
-                        : "不明";\r
-            }\r
+            for (var i = 0; i < ship.Length; i++)\r
+                ship[i].Text = _sniffer.NDock[i].Name;\r
         }\r
 \r
         private void UpdateShipInfo()\r
@@ -429,38 +117,23 @@ namespace KancolleSniffer
             var cond = new[] {labelCond1, labelCond2, labelCond3, labelCond4, labelCond5, labelCond6};\r
             var next = new[] {labelNextLv1, labelNextLv2, labelNextLv3, labelNextLv4, labelNextLv5, labelNextLv6};\r
 \r
-            if (_shipStatuses.Count == 0)\r
-                return;\r
-            for (var i = 0; i < _deckShips.Count(); i++)\r
+            var stats = _sniffer.ShipStatuses;\r
+            for (var i = 0; i < stats.Length; i++)\r
             {\r
-                var id = _deckShips[i];\r
-                ShipState info;\r
-                if (id == -1 || id == 0 || !_shipStatuses.TryGetValue(id, out info))\r
-                {\r
-                    name[i].Text = "";\r
-                    lv[i].Text = "0";\r
-                    hp[i].Text = "0/0";\r
-                    hp[i].BackColor = DefaultBackColor;\r
-                    cond[i].Text = "0";\r
-                    cond[i].BackColor = DefaultBackColor;\r
-                    next[i].Text = "0";\r
-                    continue;\r
-                }\r
-                string text;\r
-                name[i].Text = _shipNames.TryGetValue(info.ShipId, out text) ? text : "不明";\r
-                lv[i].Text = info.Level.ToString("D");\r
-                hp[i].Text = string.Format("{0:D}/{1:D}", info.NowHp, info.MaxHp);\r
-                SetHpLavel(hp[i], info.NowHp, info.MaxHp);\r
-                SetCondLabel(cond[i], info.Cond);\r
-                next[i].Text = info.ExpToNext.ToString("D");\r
+                var stat = stats[i];\r
+                name[i].Text = stat.Name;\r
+                lv[i].Text = stat.Level.ToString("D");\r
+                hp[i].Text = string.Format("{0:D}/{1:D}", stat.NowHp, stat.MaxHp);\r
+                SetHpLavel(hp[i], stat.NowHp, stat.MaxHp);\r
+                SetCondLabel(cond[i], stat.Cond);\r
+                next[i].Text = stat.ExpToNext.ToString("D");\r
             }\r
-            UpdateSlotCount();\r
         }\r
 \r
         private void SetHpLavel(Label label, int now, int max)\r
         {\r
             label.Text = string.Format("{0:D}/{1:D}", now, max);\r
-            var damage = (double)now / max;\r
+            var damage = max == 0 ? 1 : (double)now / max;\r
             label.BackColor = damage > 0.5 ? DefaultBackColor : damage > 0.25 ? Color.Orange : Color.Red;\r
         }\r
 \r
@@ -469,39 +142,41 @@ namespace KancolleSniffer
             label.Text = cond.ToString("D");\r
             label.BackColor = cond >= 50\r
                 ? Color.Yellow\r
-                : cond >= 30 ? DefaultBackColor : cond >= 20 ? Color.Orange : Color.Red;\r
+                : cond >= 30 || cond == 0\r
+                    ? DefaultBackColor\r
+                    : cond >= 20 ? Color.Orange : Color.Red;\r
         }\r
 \r
         private void UpdateTimers()\r
         {\r
             var mission = new[] {labelMission1, labelMission2, labelMission3};\r
-            var i = 0;\r
-            foreach (var timer in _missionTimers)\r
+            for (var i = 0; i < mission.Length; i++)\r
             {\r
+                var timer = _sniffer.Missions[i].Timer;\r
                 timer.Update();\r
-                SetTimerLabel(timer, mission[i++]);\r
+                SetTimerLabel(timer, mission[i]);\r
                 if (!timer.NeedRing)\r
                     continue;\r
                 Ring();\r
                 timer.NeedRing = false;\r
             }\r
             var ndock = new[] {labelRepair1, labelRepair2, labelRepair3, labelRepair4};\r
-            i = 0;\r
-            foreach (var timer in _ndocTimers)\r
+            for (var i = 0; i < ndock.Length; i++)\r
             {\r
+                var timer = _sniffer.NDock[i].Timer;\r
                 timer.Update();\r
-                SetTimerLabel(timer, ndock[i++]);\r
+                SetTimerLabel(timer, ndock[i]);\r
                 if (!timer.NeedRing)\r
                     continue;\r
                 Ring();\r
                 timer.NeedRing = false;\r
             }\r
             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
-            i = 0;\r
-            foreach (var timer in _kdocTimers)\r
+            for (var i = 0; i < kdock.Length; i++)\r
             {\r
+                var timer = _sniffer.KDock[i];\r
                 timer.Update();\r
-                SetTimerLabel(timer, kdock[i++]);\r
+                SetTimerLabel(timer, kdock[i]);\r
                 if (!timer.NeedRing)\r
                     continue;\r
                 Ring();\r
@@ -523,9 +198,9 @@ namespace KancolleSniffer
         {\r
             var label = new[] {labelCondTimer1, labelCondTimer2, labelCondTimer3};\r
             var now = DateTime.Now;\r
-            for (var i = 0; i < label.Count(); i++)\r
+            for (var i = 0; i < label.Length; i++)\r
             {\r
-                var timer = _condEndTime[i];\r
+                var timer = _sniffer.RecoveryTimes[i];\r
                 label[i].Text = timer != DateTime.MinValue && timer > now ? (timer - now).ToString(@"mm\:ss") : "00:00";\r
             }\r
         }\r
@@ -535,14 +210,14 @@ namespace KancolleSniffer
             var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5};\r
             var progress = new[] {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5};\r
             var i = 0;\r
-            foreach (var quest in _questList.Values)\r
+            foreach (var quest in _sniffer.Quests)\r
             {\r
-                if (i == progress.Count())\r
+                if (i == progress.Length)\r
                     break;\r
                 name[i].Text = quest.Name;\r
                 progress[i++].Text = string.Format("{0:D}%", quest.Progress);\r
             }\r
-            for (; i < progress.Count(); i++)\r
+            for (; i < progress.Length; i++)\r
             {\r
                 name[i].Text = "";\r
                 progress[i].Text = "";\r
@@ -573,55 +248,5 @@ namespace KancolleSniffer
 \r
         [DllImport("user32.dll")]\r
         private static extern Int32 FlashWindowEx(ref FLASHWINFO pwfi);\r
-\r
-        private class RingTimer\r
-        {\r
-            private bool _ringed;\r
-            private DateTime _endTime;\r
-            private TimeSpan _rest;\r
-\r
-            public double EndTime\r
-            {\r
-                set\r
-                {\r
-// ReSharper disable once CompareOfFloatsByEqualityOperator\r
-                    if (value != 0)\r
-                        _endTime = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(value / 1000);\r
-                    else\r
-                    {\r
-                        _endTime = DateTime.MinValue;\r
-                        _ringed = false;\r
-                    }\r
-                }\r
-            }\r
-\r
-            public void Update()\r
-            {\r
-                if (_endTime == DateTime.MinValue)\r
-                {\r
-                    _rest = TimeSpan.Zero;\r
-                    return;\r
-                }\r
-                _rest = _endTime - DateTime.Now;\r
-                if (_rest < TimeSpan.Zero)\r
-                    _rest = TimeSpan.Zero;\r
-                if (_rest >= TimeSpan.FromMinutes(1) || _ringed)\r
-                    return;\r
-                _ringed = true;\r
-                NeedRing = true;\r
-            }\r
-\r
-            public bool NeedRing { get; set; }\r
-\r
-            public bool IsSet\r
-            {\r
-                get { return _endTime != DateTime.MinValue; }\r
-            }\r
-\r
-            public override string ToString()\r
-            {\r
-                return _rest.Days == 0 ? _rest.ToString(@"hh\:mm\:ss") : _rest.ToString(@"d\.hh\:mm");\r
-            }\r
-        }\r
     }\r
 }
\ No newline at end of file
diff --git a/KancolleSniffer/MissionInfo.cs b/KancolleSniffer/MissionInfo.cs
new file mode 100644 (file)
index 0000000..cd9f312
--- /dev/null
@@ -0,0 +1,89 @@
+// Copyright (C) 2013 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// \r
+// This program is part of KancolleSniffer.\r
+//\r
+// KancolleSniffer is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+\r
+using System.Collections.Generic;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Windows.Forms;\r
+using Codeplex.Data;\r
+\r
+namespace KancolleSniffer\r
+{\r
+    public class MissionInfo\r
+    {\r
+        private readonly Dictionary<int, string> _missionNames = new Dictionary<int, string>();\r
+        private readonly NameAndTimer[] _missions = new NameAndTimer[3];\r
+\r
+        private readonly string _missionNamesFile =\r
+            Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "missions.json");\r
+\r
+        public MissionInfo()\r
+        {\r
+            for (var i = 0; i < _missions.Length; i++)\r
+                _missions[i] = new NameAndTimer();\r
+        }\r
+\r
+        public void InspectMission(dynamic json)\r
+        {\r
+            foreach (var entry in json)\r
+                _missionNames[(int)entry.api_id] = (string)entry.api_name;\r
+        }\r
+\r
+        public void LoadNames()\r
+        {\r
+            try\r
+            {\r
+                InspectMission(DynamicJson.Parse(File.ReadAllText(_missionNamesFile)));\r
+            }\r
+            catch (FileNotFoundException)\r
+            {\r
+            }\r
+        }\r
+\r
+        public void SaveNames()\r
+        {\r
+            var ship = from data in _missionNames select new {api_id = data.Key, api_name = data.Value};\r
+            File.WriteAllText(_missionNamesFile, DynamicJson.Serialize(ship));\r
+        }\r
+\r
+        public void InspectDeck(dynamic json)\r
+        {\r
+            foreach (var entry in json)\r
+            {\r
+                var id = (int)entry.api_id;\r
+                if (id == 1)\r
+                    continue;\r
+                id -= 2;\r
+                var mission = entry.api_mission;\r
+                if (mission[0] == 0)\r
+                {\r
+                    _missions[id].Name = "";\r
+                    _missions[id].Timer.EndTime = 0;\r
+                    continue;\r
+                }\r
+                string name;\r
+                _missions[id].Name = _missionNames.TryGetValue((int)mission[1], out name) ? name : "不明";\r
+                _missions[id].Timer.EndTime = mission[2];\r
+            }\r
+        }\r
+\r
+        public NameAndTimer[] Missions\r
+        {\r
+            get { return _missions; }\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/KancolleSniffer/QuestInfo.cs b/KancolleSniffer/QuestInfo.cs
new file mode 100644 (file)
index 0000000..291c696
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright (C) 2013 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// \r
+// This program is part of KancolleSniffer.\r
+//\r
+// KancolleSniffer is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Linq;\r
+\r
+namespace KancolleSniffer\r
+{\r
+    public class QuestInfo\r
+    {\r
+        private DateTime _lastCreared;\r
+        private readonly SortedDictionary<int, NameAndProgress> _quests = new SortedDictionary<int, NameAndProgress>();\r
+\r
+        public void Inspect(dynamic json)\r
+        {\r
+            var resetTime = DateTime.Today.AddHours(5);\r
+            if (DateTime.Now >= resetTime && _lastCreared < resetTime)\r
+            {\r
+                _quests.Clear(); // 前日に未消化のデイリーを消す。\r
+                _lastCreared = DateTime.Now;\r
+            }\r
+            foreach (var entry in json.api_list)\r
+            {\r
+                if (entry is double) // -1の場合がある。\r
+                    continue;\r
+\r
+                var id = (int)entry.api_no;\r
+                var state = (int)entry.api_state;\r
+                var progress = (int)entry.api_progress_flag;\r
+                var name = (string)entry.api_title;\r
+\r
+                switch (progress)\r
+                {\r
+                    case 0:\r
+                        break;\r
+                    case 1:\r
+                        progress = 50;\r
+                        break;\r
+                    case 2:\r
+                        progress = 80;\r
+                        break;\r
+                }\r
+                switch (state)\r
+                {\r
+                    case 2:\r
+                        _quests[id] = new NameAndProgress {Name = name, Progress = progress};\r
+                        break;\r
+                    case 1:\r
+                    case 3:\r
+                        _quests.Remove(id);\r
+                        continue;\r
+                }\r
+            }\r
+        }\r
+\r
+        public NameAndProgress[] Quests\r
+        {\r
+            get { return _quests.Values.ToArray(); }\r
+        }\r
+\r
+        public struct NameAndProgress\r
+        {\r
+            public string Name { get; set; }\r
+            public int Progress { get; set; }\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/KancolleSniffer/ShipInfo.cs b/KancolleSniffer/ShipInfo.cs
new file mode 100644 (file)
index 0000000..d01065d
--- /dev/null
@@ -0,0 +1,173 @@
+// Copyright (C) 2013 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// \r
+// This program is part of KancolleSniffer.\r
+//\r
+// KancolleSniffer is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+\r
+using System;\r
+using System.Collections.Generic;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Windows.Forms;\r
+using Codeplex.Data;\r
+\r
+namespace KancolleSniffer\r
+{\r
+    public class ShipInfo\r
+    {\r
+        private readonly Dictionary<int, string> _shipNames = new Dictionary<int, string>();\r
+        private readonly int[] _deck = {-1, -1, -1, -1, -1, -1};\r
+        private readonly Dictionary<int, ShipStatus> _shipInfo = new Dictionary<int, ShipStatus>();\r
+        private readonly DateTime[] _recoveryTimes = new DateTime[3];\r
+        private bool _updateRecoveryTimes;\r
+\r
+        private readonly string _shipNamesFile =\r
+            Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "shipnames.json");\r
+\r
+        public string FleetName { get; set; }\r
+\r
+        public DateTime[] RecoveryTimes\r
+        {\r
+            get { return _recoveryTimes; }\r
+        }\r
+\r
+        public void InspectShip(dynamic json)\r
+        {\r
+            foreach (var entry in json)\r
+                _shipNames[(int)entry.api_ship_id] = (string)entry.api_name;\r
+        }\r
+\r
+        public void LoadNames()\r
+        {\r
+            try\r
+            {\r
+                InspectShip(DynamicJson.Parse(File.ReadAllText(_shipNamesFile)));\r
+            }\r
+            catch (FileNotFoundException)\r
+            {\r
+            }\r
+        }\r
+\r
+        public void SaveNames()\r
+        {\r
+            var ship = from data in _shipNames select new {api_ship_id = data.Key, api_name = data.Value};\r
+            File.WriteAllText(_shipNamesFile, DynamicJson.Serialize(ship));\r
+        }\r
+\r
+        public string GetNameByShipId(int shipId)\r
+        {\r
+            string name;\r
+            return _shipNames.TryGetValue(shipId, out name) ? name : "不明";\r
+        }\r
+\r
+        public string GetNameById(int id)\r
+        {\r
+            ShipStatus ship;\r
+            return _shipInfo.TryGetValue(id, out ship) ? GetNameByShipId(ship.ShipId) : "不明";\r
+        }\r
+\r
+        public void InspectBattleResult(dynamic json)\r
+        {\r
+            if (!json.IsDefined("api_get_ship")) // 艦娘がドロップしていない。\r
+                return;\r
+            var entry = json.api_get_ship;\r
+            _shipNames[(int)entry.api_ship_id] = (string)entry.api_ship_name;\r
+        }\r
+\r
+        public void InspectDeck(dynamic json, bool port)\r
+        {\r
+            if (!port)\r
+                _updateRecoveryTimes = true;\r
+\r
+            foreach (var entry in json)\r
+            {\r
+                var fleet = (int)entry.api_id;\r
+                if (fleet != 1)\r
+                    continue;\r
+                FleetName = (string)entry.api_name;\r
+                for (var i = 0; i < _deck.Length; i++)\r
+                {\r
+                    var id = (int)entry.api_ship[i];\r
+                    if (_deck[i] == id)\r
+                        continue;\r
+                    _deck[i] = id;\r
+                    _updateRecoveryTimes = true;\r
+                }\r
+            }\r
+        }\r
+\r
+        public int NumShips\r
+        {\r
+            get { return _shipInfo.Count; }\r
+        }\r
+\r
+        public void InspectShipInfo(dynamic json)\r
+        {\r
+            _shipInfo.Clear();\r
+            foreach (var entry in json)\r
+            {\r
+                _shipInfo[(int)entry.api_id] = new ShipStatus\r
+                {\r
+                    ShipId = (int)entry.api_ship_id,\r
+                    Level = (int)entry.api_lv,\r
+                    ExpToNext = (int)entry.api_exp[1],\r
+                    MaxHp = (int)entry.api_maxhp,\r
+                    NowHp = (int)entry.api_nowhp,\r
+                    Cond = (int)entry.api_cond\r
+                };\r
+            }\r
+            SetRecoveryTime();\r
+        }\r
+\r
+        private void SetRecoveryTime()\r
+        {\r
+            if (!_updateRecoveryTimes)\r
+                return;\r
+            var cond =\r
+                (from id in _deck where _shipInfo.ContainsKey(id) select _shipInfo[id].Cond).DefaultIfEmpty(49).Min();\r
+            var thresh = new[] { 30, 40, 49 };\r
+            for (var i = 0; i < thresh.Length; i++)\r
+                _recoveryTimes[i] = cond < thresh[i] ? DateTime.Now.AddMinutes((thresh[i] - cond + 2) / 3 * 3) : DateTime.MinValue;            \r
+        }\r
+\r
+        public ShipStatus[] ShipStatuses\r
+        {\r
+            get\r
+            {\r
+                var result = new ShipStatus[_deck.Length];\r
+                for (var i = 0; i < _deck.Length; i++)\r
+                {\r
+                    var id = _deck[i];\r
+                    ShipStatus status;\r
+                    if (id == -1 || !_shipInfo.TryGetValue(id, out status))\r
+                        continue;\r
+                    status.Name = GetNameByShipId(status.ShipId);\r
+                    result[i] = status;\r
+                }\r
+                return result;\r
+            }\r
+        }\r
+    }\r
+\r
+    public struct ShipStatus\r
+    {\r
+        public int ShipId { get; set; }\r
+        public string Name { get; set; }\r
+        public int Level { get; set; }\r
+        public int ExpToNext { get; set; }\r
+        public int MaxHp { get; set; }\r
+        public int NowHp { get; set; }\r
+        public int Cond { get; set; }\r
+    }\r
+}
\ No newline at end of file
diff --git a/KancolleSniffer/Sniffer.cs b/KancolleSniffer/Sniffer.cs
new file mode 100644 (file)
index 0000000..71a3785
--- /dev/null
@@ -0,0 +1,249 @@
+// Copyright (C) 2013 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// \r
+// This program is part of KancolleSniffer.\r
+//\r
+// KancolleSniffer is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+\r
+using System;\r
+\r
+namespace KancolleSniffer\r
+{\r
+    [Flags]\r
+    public enum UpdateInfo\r
+    {\r
+        None = 0,\r
+        Item = 1,\r
+        Ship = 2,\r
+        Timer = 4,\r
+        Quest = 8,\r
+        NDock = 16,\r
+        Mission = 32\r
+    }\r
+\r
+    public class Sniffer\r
+    {\r
+        private readonly NameAndTimer[] _ndocInfo = new NameAndTimer[4];\r
+        private readonly RingTimer[] _kdocTimers = new RingTimer[4];\r
+        private readonly ItemInfo _itemInfo = new ItemInfo();\r
+        private readonly QuestInfo _questInfo = new QuestInfo();\r
+        private readonly MissionInfo _missionInfo = new MissionInfo();\r
+        private readonly ShipInfo _shipInfo = new ShipInfo();\r
+\r
+        public Sniffer()\r
+        {\r
+            for (var i = 0; i < _ndocInfo.Length; i++)\r
+                _ndocInfo[i] = new NameAndTimer();\r
+            for (var i = 0; i < _kdocTimers.Length; i++)\r
+                _kdocTimers[i] = new RingTimer();\r
+        }\r
+\r
+        public UpdateInfo Sniff(string uri, dynamic json)\r
+        {\r
+            if (uri.EndsWith("api_get_member/ship"))\r
+            {\r
+                _shipInfo.InspectShip(json);\r
+                return UpdateInfo.None;\r
+            }\r
+            if (uri.EndsWith("api_get_member/basic"))\r
+            {\r
+                _itemInfo.InspectBasic(json);\r
+                return UpdateInfo.Item;\r
+            }\r
+            if (uri.EndsWith("api_get_member/record"))\r
+            {\r
+                _itemInfo.InspectRecord(json);\r
+                return UpdateInfo.Item;\r
+            }\r
+            if (uri.EndsWith("api_get_member/material"))\r
+            {\r
+                _itemInfo.InspectMaterial(json);\r
+                return UpdateInfo.Item;\r
+            }\r
+            if (uri.EndsWith("api_get_member/slotitem"))\r
+            {\r
+                _itemInfo.InspectSlotItem(json);\r
+                return UpdateInfo.Item;\r
+            }\r
+            if (uri.EndsWith("api_get_member/questlist"))\r
+            {\r
+                _questInfo.Inspect(json);\r
+                return UpdateInfo.Quest;\r
+            }\r
+            if (uri.EndsWith("api_get_member/ndock"))\r
+            {\r
+                InspectNDock(json);\r
+                return UpdateInfo.NDock | UpdateInfo.Timer;\r
+            }\r
+            if (uri.EndsWith("api_get_member/kdock"))\r
+            {\r
+                InspectKDock(json);\r
+                return UpdateInfo.Timer;\r
+            }\r
+            if (uri.EndsWith("api_get_master/mission"))\r
+            {\r
+                _missionInfo.InspectMission(json);\r
+                return UpdateInfo.Mission;\r
+            }\r
+            if (uri.Contains("api_get_member/deck"))\r
+            {\r
+                _missionInfo.InspectDeck(json);\r
+                _shipInfo.InspectDeck(json, uri.EndsWith("deck_port"));\r
+                return UpdateInfo.Mission | UpdateInfo.Ship;\r
+            }\r
+            if (uri.EndsWith("api_req_sortie/battleresult"))\r
+            {\r
+                _shipInfo.InspectBattleResult(json);\r
+                return UpdateInfo.None;\r
+            }\r
+            if (uri.EndsWith("api_get_member/ship2") || uri.EndsWith("api_get_member/ship3"))\r
+            {\r
+                _shipInfo.InspectShipInfo(uri.EndsWith("ship3") ? json.api_ship_data : json);\r
+                _itemInfo.NowShips = _shipInfo.NumShips;\r
+                return UpdateInfo.Ship | UpdateInfo.Item | UpdateInfo.Timer;\r
+            }\r
+            return UpdateInfo.None;\r
+        }\r
+\r
+        public void SaveNames()\r
+        {\r
+            _missionInfo.SaveNames();\r
+            _shipInfo.SaveNames();\r
+        }\r
+\r
+        public void LoadNames()\r
+        {\r
+            _missionInfo.LoadNames();\r
+            _shipInfo.LoadNames();\r
+        }\r
+\r
+        private void InspectNDock(dynamic json)\r
+        {\r
+            foreach (var entry in json)\r
+            {\r
+                var id = (int)entry.api_id;\r
+                _ndocInfo[id - 1].Timer.EndTime = (double)entry.api_complete_time;\r
+                var ship = (int)entry.api_ship_id;\r
+                _ndocInfo[id - 1].Name = ship == 0 ? "" : _shipInfo.GetNameById(ship);\r
+            }\r
+        }\r
+\r
+        public NameAndTimer[] NDock\r
+        {\r
+            get { return _ndocInfo; }\r
+        }\r
+\r
+        private void InspectKDock(dynamic json)\r
+        {\r
+            foreach (var entry in json)\r
+                _kdocTimers[(int)entry.api_id - 1].EndTime = (double)entry.api_complete_time;\r
+        }\r
+\r
+        public RingTimer[] KDock\r
+        {\r
+            get { return _kdocTimers; }\r
+        }\r
+\r
+        public ItemInfo Item\r
+        {\r
+            get { return _itemInfo; }\r
+        }\r
+\r
+        public QuestInfo.NameAndProgress[] Quests\r
+        {\r
+            get { return _questInfo.Quests; }\r
+        }\r
+\r
+        public NameAndTimer[] Missions\r
+        {\r
+            get { return _missionInfo.Missions; }\r
+        }\r
+\r
+        public string FleetName\r
+        {\r
+            get { return _shipInfo.FleetName; }\r
+        }\r
+\r
+        public DateTime[] RecoveryTimes\r
+        {\r
+            get { return _shipInfo.RecoveryTimes; }\r
+        }\r
+\r
+        public ShipStatus[] ShipStatuses\r
+        {\r
+            get { return _shipInfo.ShipStatuses; }\r
+        }\r
+    }\r
+\r
+    public class NameAndTimer\r
+    {\r
+        public string Name { get; set; }\r
+\r
+        public RingTimer Timer { get; set; }\r
+\r
+        public NameAndTimer()\r
+        {\r
+            Timer = new RingTimer();\r
+        }\r
+    }\r
+\r
+    public class RingTimer\r
+    {\r
+        private bool _ringed;\r
+        private DateTime _endTime;\r
+        private TimeSpan _rest;\r
+\r
+        public double EndTime\r
+        {\r
+            set\r
+            {\r
+// ReSharper disable once CompareOfFloatsByEqualityOperator\r
+                if (value != 0)\r
+                    _endTime = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(value / 1000);\r
+                else\r
+                {\r
+                    _endTime = DateTime.MinValue;\r
+                    _ringed = false;\r
+                }\r
+            }\r
+        }\r
+\r
+        public void Update()\r
+        {\r
+            if (_endTime == DateTime.MinValue)\r
+            {\r
+                _rest = TimeSpan.Zero;\r
+                return;\r
+            }\r
+            _rest = _endTime - DateTime.Now;\r
+            if (_rest < TimeSpan.Zero)\r
+                _rest = TimeSpan.Zero;\r
+            if (_rest >= TimeSpan.FromMinutes(1) || _ringed)\r
+                return;\r
+            _ringed = true;\r
+            NeedRing = true;\r
+        }\r
+\r
+        public bool NeedRing { get; set; }\r
+\r
+        public bool IsSet\r
+        {\r
+            get { return _endTime != DateTime.MinValue; }\r
+        }\r
+\r
+        public override string ToString()\r
+        {\r
+            return _rest.Days == 0 ? _rest.ToString(@"hh\:mm\:ss") : _rest.ToString(@"d\.hh\:mm");\r
+        }\r
+    }\r
+}
\ No newline at end of file