OSDN Git Service

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