OSDN Git Service

ブラウザと並列に動作するようにする
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / AkashiTimer.cs
1 // Copyright (C) 2014, 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.Linq;\r
18 \r
19 namespace KancolleSniffer\r
20 {\r
21     public class AkashiTimer\r
22     {\r
23         private readonly ShipInfo _shipInfo;\r
24         private readonly DockInfo _dockInfo;\r
25         private readonly PresetDeck _presetDeck;\r
26         private readonly RepairStatus[] _repairStatuses = new RepairStatus[ShipInfo.FleetCount];\r
27         private DateTime _start;\r
28 \r
29         public class RepairSpan\r
30         {\r
31             public int Diff { get; set; }\r
32             public TimeSpan Span { get; set; }\r
33 \r
34             public RepairSpan(int diff, TimeSpan span)\r
35             {\r
36                 Diff = diff;\r
37                 Span = span;\r
38             }\r
39         }\r
40 \r
41         private class RepairStatus\r
42         {\r
43             private ShipStatus[] _target = new ShipStatus[0];\r
44             private int[] _deck = new int[0];\r
45             private TimeSpan FirstRepairTime => TimeSpan.FromMinutes(20);\r
46 \r
47             private bool PassedFirstRepairTime(DateTime start, DateTime prev, DateTime now) =>\r
48                 prev - start < FirstRepairTime && now - start >= FirstRepairTime;\r
49 \r
50             private TimeSpan RepairTime(ShipStatus ship, int damage) =>\r
51                 TimeSpan.FromMinutes(Math.Ceiling(ship.RepairTime.TotalMinutes / (ship.MaxHp - ship.NowHp) * damage));\r
52 \r
53 \r
54             public int[] Deck\r
55             {\r
56                 set => _deck = value;\r
57             }\r
58 \r
59             public State State { get; set; }\r
60 \r
61             public bool IsRepaired(ShipStatus[] target) => _target.Zip(target, (a, b) => a.NowHp < b.NowHp).Any(x => x);\r
62 \r
63             public bool DeckChanged(IEnumerable<int> deck) => !_deck.SequenceEqual(deck);\r
64 \r
65             public void UpdateTarget(ShipStatus[] target)\r
66             {\r
67                 _target = target;\r
68             }\r
69 \r
70             public RepairSpan[] GetTimers(DateTime start, DateTime now)\r
71             {\r
72                 var spent = TimeSpan.FromSeconds((int)(now - start).TotalSeconds);\r
73                 return _target.Select(s =>\r
74                 {\r
75                     var damage = s.MaxHp - s.NowHp;\r
76                     if (damage == 0)\r
77                         return new RepairSpan(0, TimeSpan.MinValue);\r
78                     if (spent < FirstRepairTime)\r
79                         return new RepairSpan(0, FirstRepairTime - spent);\r
80                     if (damage == 1)\r
81                         return new RepairSpan(1, TimeSpan.Zero);\r
82                     for (var d = 2; d <= damage; d++)\r
83                     {\r
84                         var span = RepairTime(s, d);\r
85                         if (span <= FirstRepairTime)\r
86                             continue;\r
87                         if (span > spent)\r
88                             return new RepairSpan(d - 1, span - spent);\r
89                     }\r
90                     return new RepairSpan(damage, TimeSpan.Zero);\r
91                 }).ToArray();\r
92             }\r
93 \r
94             public Notice GetNotice(DateTime start, DateTime prev, DateTime now)\r
95             {\r
96                 var proc = new List<string>();\r
97                 var comp = new List<string>();\r
98                 foreach (var s in _target)\r
99                 {\r
100                     var damage = s.MaxHp - s.NowHp;\r
101                     if (damage == 0)\r
102                         continue;\r
103                     if (damage == 1)\r
104                     {\r
105                         if (PassedFirstRepairTime(start, prev, now))\r
106                             comp.Add(s.Name);\r
107                         continue;\r
108                     }\r
109                     // スリープで時間が飛んだときに修理完了だけを表示するために、\r
110                     // 完全回復から減らしながら所要時間と経過時間と比較する。\r
111                     for (var d = damage; d >= 2; d--)\r
112                     {\r
113                         var span = RepairTime(s, d);\r
114                         if (span <= FirstRepairTime)\r
115                         {\r
116                             if (d == damage && PassedFirstRepairTime(start, prev, now))\r
117                                 comp.Add(s.Name);\r
118                             continue;\r
119                         }\r
120                         if (span <= prev - start || now - start < span)\r
121                             continue;\r
122                         if (d == damage)\r
123                             comp.Add(s.Name);\r
124                         else\r
125                             proc.Add(s.Name);\r
126                         break;\r
127                     }\r
128                 }\r
129                 return new Notice\r
130                 {\r
131                     Proceeded = proc.Count == 0 ? "" : string.Join(" ", proc),\r
132                     Completed = comp.Count == 0 ? "" : string.Join(" ", comp)\r
133                 };\r
134             }\r
135         }\r
136 \r
137         public class Notice\r
138         {\r
139             public string Proceeded { get; set; }\r
140             public string Completed { get; set; }\r
141         }\r
142 \r
143         public AkashiTimer(ShipInfo ship, DockInfo dock, PresetDeck preset)\r
144         {\r
145             _shipInfo = ship;\r
146             _dockInfo = dock;\r
147             _presetDeck = preset;\r
148             for (var i = 0; i < _repairStatuses.Length; i++)\r
149                 _repairStatuses[i] = new RepairStatus();\r
150         }\r
151 \r
152         private enum State\r
153         {\r
154             Continue = 0,\r
155             Reset = 1,\r
156         }\r
157 \r
158         public void Port()\r
159         {\r
160             CheckFleet();\r
161             var now = DateTime.Now;\r
162             var reset = _repairStatuses.Any(r => r.State == State.Reset);\r
163             if (_start == DateTime.MinValue || now - _start > TimeSpan.FromMinutes(20) || reset)\r
164                 _start = now;\r
165         }\r
166 \r
167         public void InspectChange(string request)\r
168         {\r
169             CheckFleet();\r
170             var values = HttpUtility.ParseQueryString(request);\r
171             if (int.Parse(values["api_ship_idx"]) == -1)\r
172                 return;\r
173             if (_repairStatuses.Any(r => r.State == State.Reset))\r
174                 _start = DateTime.Now;\r
175         }\r
176 \r
177         public void CheckFleet()\r
178         {\r
179             for (var fleet = 0; fleet < ShipInfo.FleetCount; fleet++)\r
180                 CheckFleet(fleet);\r
181         }\r
182 \r
183         private void CheckFleet(int fleet)\r
184         {\r
185             var deck = _shipInfo.GetDeck(fleet).ToArray();\r
186             var repair = _repairStatuses[fleet];\r
187             var fs = _shipInfo.GetStatus(deck[0]);\r
188             repair.State = State.Continue;\r
189             if (!fs.Spec.IsRepairShip)\r
190             {\r
191                 repair.UpdateTarget(new ShipStatus[0]);\r
192                 repair.Deck = deck;\r
193                 return;\r
194             }\r
195             if (repair.DeckChanged(deck))\r
196             {\r
197                 repair.State = State.Reset;\r
198                 repair.Deck = deck;\r
199             }\r
200             var target = RepairTarget(deck);\r
201             if (repair.IsRepaired(target))\r
202                 repair.State = State.Reset;\r
203             repair.UpdateTarget(target);\r
204         }\r
205 \r
206         private ShipStatus[] RepairTarget(int[] deck)\r
207         {\r
208             var fs = _shipInfo.GetStatus(deck[0]);\r
209             if (!fs.Spec.IsRepairShip || _dockInfo.InNDock(fs.Id) || fs.DamageLevel >= ShipStatus.Damage.Half)\r
210                 return new ShipStatus[0];\r
211             var cap = fs.Slot.Count(item => item.Spec.IsRepairFacility) + 2;\r
212             /*\r
213              * 泊地修理の条件を満たさない艦はMaxHp==NowHpのダミーを設定する。\r
214              * - 入渠中の艦娘は終わったときに回復扱いされないようNowHp=MaxHpに\r
215              * - 中破以上でNowHp=MaxHpにすると回復扱いされるのでNowHp=MaxHp=0に\r
216             */\r
217             return (from id in deck.Take(cap)\r
218                 let s = (ShipStatus)_shipInfo.GetStatus(id).Clone()\r
219                 let full = new ShipStatus {NowHp = s.MaxHp, MaxHp = s.MaxHp}\r
220                 let zero = new ShipStatus()\r
221                 select _dockInfo.InNDock(id) ? full : s.DamageLevel >= ShipStatus.Damage.Half ? zero : s).ToArray();\r
222         }\r
223 \r
224         public RepairSpan[] GetTimers(int fleet)\r
225             => _start == DateTime.MinValue ? new RepairSpan[0] : _repairStatuses[fleet].GetTimers(_start, DateTime.Now);\r
226 \r
227         public TimeSpan PresetDeckTimer\r
228         {\r
229             get\r
230             {\r
231                 if (_start == DateTime.MinValue)\r
232                     return TimeSpan.MinValue;\r
233                 var r = TimeSpan.FromMinutes(20) - TimeSpan.FromSeconds((int)(DateTime.Now - _start).TotalSeconds);\r
234                 return r >= TimeSpan.Zero ? r : TimeSpan.Zero;\r
235             }\r
236         }\r
237 \r
238         public bool CheckRepairing(int fleet) => GetTimers(fleet).Any(r => r.Span != TimeSpan.MinValue);\r
239 \r
240         public bool CheckRepairing() => Enumerable.Range(0, ShipInfo.FleetCount).Any(CheckRepairing);\r
241 \r
242         public bool CheckPresetRepairing()\r
243             => _presetDeck.Decks.Where(deck => deck != null)\r
244                 .Any(deck => RepairTarget(deck).Any(s => s.NowHp < s.MaxHp));\r
245 \r
246         public Notice[] GetNotice(DateTime prev, DateTime now)\r
247         {\r
248             if (prev == DateTime.MinValue || _start == DateTime.MinValue)\r
249                 return new Notice[0];\r
250             var r = _repairStatuses.Select(repair => repair.GetNotice(_start, prev, now)).ToArray();\r
251             var m20 = TimeSpan.FromMinutes(20);\r
252             if (prev - _start < m20 && now - _start >= m20)\r
253                 r[0].Proceeded = "20分経過しました。";\r
254             return r;\r
255         }\r
256     }\r
257 }