OSDN Git Service

神鷹の先制対潜を識別する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / ShipStatus.cs
1 // Copyright (C) 2017 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 static System.Math;\r
19 \r
20 namespace KancolleSniffer.Model\r
21 {\r
22     public class ShipStatus : ICloneable\r
23     {\r
24         public int Id { get; set; }\r
25         public bool Empty => Id == -1;\r
26         public Fleet Fleet { get; set; }\r
27         public int DeckIndex { get; set; }\r
28         public ShipSpec Spec { get; set; }\r
29 \r
30         public string Name => Spec.Name;\r
31 \r
32         public int Level { get; set; }\r
33         public int ExpToNext { get; set; }\r
34         public int MaxHp { get; set; }\r
35         public int NowHp { get; set; }\r
36         public int Cond { get; set; }\r
37         public int Fuel { get; set; }\r
38         public int Bull { get; set; }\r
39         public int[] OnSlot { get; set; }\r
40         public int NdockTime { get; set; }\r
41         public int[] NdockItem { get; set; }\r
42         public int LoS { get; set; }\r
43         public int Firepower { get; set; }\r
44         public int Torpedo { get; set; }\r
45         public int AntiSubmarine { get; set; }\r
46         public int AntiAir { get; set; }\r
47         public int Lucky { get; set; }\r
48         public bool Locked { get; set; }\r
49         public bool Escaped { get; set; }\r
50         public Attack SpecialAttack { get; set; }\r
51 \r
52         public enum Attack\r
53         {\r
54             None,\r
55             Fire,\r
56             Fired\r
57         }\r
58 \r
59         public Damage DamageLevel => CalcDamage(NowHp, MaxHp);\r
60 \r
61         private IList<ItemStatus> _slot;\r
62         private ItemStatus _slotEx;\r
63         public Func<ItemStatus, ItemStatus> GetItem { get; set; } = item => item;\r
64 \r
65         public IReadOnlyList<ItemStatus> Slot\r
66         {\r
67             get => _slot.Select(item => GetItem(item)).ToArray();\r
68             set => _slot = value.ToArray();\r
69         }\r
70 \r
71         public ItemStatus SlotEx\r
72         {\r
73             get => GetItem(_slotEx);\r
74             set => _slotEx = value;\r
75         }\r
76 \r
77         public void FreeSlot(int idx) => _slot[idx] = new ItemStatus();\r
78 \r
79         public IEnumerable<ItemStatus> AllSlot => SlotEx.Id == 0 ? Slot : Slot.Concat(new[] {SlotEx});\r
80 \r
81         public ShipStatus()\r
82         {\r
83             Id = -1;\r
84             Spec = new ShipSpec();\r
85             OnSlot = new int[0];\r
86             Slot = new ItemStatus[0];\r
87             SlotEx = new ItemStatus();\r
88         }\r
89 \r
90         public enum Damage\r
91         {\r
92             Minor,\r
93             Small,\r
94             Half,\r
95             Badly,\r
96             Sunk\r
97         }\r
98 \r
99         public void RepairShip()\r
100         {\r
101             NowHp = MaxHp;\r
102             Cond = Max(40, Cond);\r
103         }\r
104 \r
105         public static Damage CalcDamage(int now, int max)\r
106         {\r
107             if (now == 0 && max > 0)\r
108                 return Damage.Sunk;\r
109             var ratio = max == 0 ? 1 : (double)now / max;\r
110             return ratio > 0.75 ? Damage.Minor :\r
111                 ratio > 0.5 ? Damage.Small :\r
112                 ratio > 0.25 ? Damage.Half : Damage.Badly;\r
113         }\r
114 \r
115         public TimeSpan RepairTime => TimeSpan.FromSeconds((int)(RepairTimePerHp.TotalSeconds * (MaxHp - NowHp)) + 30);\r
116 \r
117         public TimeSpan RepairTimePerHp =>\r
118             TimeSpan.FromSeconds(Spec.RepairWeight *\r
119                                  (Level < 12\r
120                                      ? Level * 10\r
121                                      : Level * 5 + Floor(Sqrt(Level - 11)) * 10 + 50));\r
122 \r
123         public double EffectiveFirepower\r
124         {\r
125             get\r
126             {\r
127                 if (Spec.IsSubmarine)\r
128                     return 0;\r
129                 var isRyuseiAttack = Spec.Id == 352 && // 速吸改\r
130                                      Slot.Any(item => item.Spec.Type == 8); // 艦攻装備\r
131                 var levelBonus = AllSlot.Sum(item => item.FirepowerLevelBonus);\r
132                 if (!Spec.IsAircraftCarrier && !isRyuseiAttack)\r
133                     return Firepower + levelBonus + Fleet.CombinedFirepowerBonus + 5;\r
134                 var specs = (from item in Slot where item.Spec.IsAircraft select item.Spec).ToArray();\r
135                 var torpedo = specs.Sum(s => s.Torpedo);\r
136                 var bomber = specs.Sum(s => s.Bomber);\r
137                 if (torpedo == 0 && bomber == 0)\r
138                     return 0;\r
139                 return (int)((Firepower + torpedo + levelBonus +\r
140                               (int)(bomber * 1.3) + Fleet.CombinedFirepowerBonus) * 1.5) + 55;\r
141             }\r
142         }\r
143 \r
144         public double EffectiveTorpedo\r
145         {\r
146             get\r
147             {\r
148                 if (Spec.IsAircraftCarrier || Torpedo == 0)\r
149                     return 0;\r
150                 return Torpedo + AllSlot.Sum(item => item.TorpedoLevelBonus) + Fleet.CombinedTorpedoPenalty + 5;\r
151             }\r
152         }\r
153 \r
154         public double EffectiveAntiSubmarine\r
155         {\r
156             get\r
157             {\r
158                 if (!Spec.IsAntiSubmarine)\r
159                     return 0;\r
160                 // ReSharper disable once CompareOfFloatsByEqualityOperator\r
161                 if (Spec.IsAircraftCarrier && EffectiveFirepower == 0 && !CanOpeningAntiSubmarineAttack)\r
162                     return 0;\r
163                 var sonar = false;\r
164                 var dct = false;\r
165                 var dc = false;\r
166                 var special = false;\r
167                 var aircraft = false;\r
168                 var all = 0.0;\r
169                 foreach (var spec in Slot.Select(item => item.Spec))\r
170                 {\r
171                     if (spec.IsSonar)\r
172                     {\r
173                         sonar = true;\r
174                     }\r
175                     else if (spec.IsDCT)\r
176                     {\r
177                         dct = true;\r
178                     }\r
179                     else if (spec.IsDC)\r
180                     {\r
181                         dc = true;\r
182                     }\r
183                     else if (spec.IsSpecialDCT)\r
184                     {\r
185                         special = true;\r
186                     }\r
187                     else if (spec.IsAircraft)\r
188                     {\r
189                         aircraft = true;\r
190                     }\r
191                     all += spec.EffectiveAntiSubmarine;\r
192                 }\r
193                 var vanilla = ShipAntiSubmarine;\r
194                 if (vanilla == 0 && !aircraft) // 素対潜0で航空機なしは対潜攻撃なし\r
195                     return 0;\r
196                 var bonus = 1.0;\r
197                 if (dct && dc)\r
198                     bonus = 1.1;\r
199                 if (sonar && (dct || dc || special))\r
200                     bonus = 1.15;\r
201                 if (sonar && dct && dc)\r
202                     bonus = 1.15 * 1.25;\r
203                 var levelBonus = Slot.Sum(item => item.AntiSubmarineLevelBonus);\r
204                 return bonus * (Sqrt(vanilla) * 2 + all * 1.5 + levelBonus + (aircraft ? 8 : 13));\r
205             }\r
206         }\r
207 \r
208         public int ShipAntiSubmarine => AntiSubmarine - Slot.Sum(item => item.Spec.AntiSubmarine);\r
209 \r
210         public bool CanOpeningAntiSubmarineAttack\r
211         {\r
212             get\r
213             {\r
214                 switch (Name)\r
215                 {\r
216                     case "五十鈴改二":\r
217                     case "龍田改二":\r
218                     case "Jervis改":\r
219                     case "Samuel B.Roberts改":\r
220                         return true;\r
221                     case "大鷹改":\r
222                     case "大鷹改二":\r
223                         return Slot.Any(item => item.Spec.IsAircraft && item.Spec.EffectiveAntiSubmarine > 0);\r
224                     case "大鷹":\r
225                     case "Gambier Bay":\r
226                     case "Gambier Bay改":\r
227                     case "瑞鳳改二乙":\r
228                     case "神鷹":\r
229                     case "神鷹改":\r
230                     case "神鷹改二":\r
231                         return Slot.Any(item => item.Spec.IsAircraft && item.Spec.EffectiveAntiSubmarine >= 7) &&\r
232                                AntiSubmarine >= (HaveSonar ? 50 : 65);\r
233                     default:\r
234                         return Spec.ShipType == 1\r
235                             ? Slot.Sum(item => item.Spec.AntiSubmarine) >= 4 && AntiSubmarine >= 75 ||\r
236                               HaveSonar && AntiSubmarine >= 60\r
237                             : HaveSonar && AntiSubmarine >= 100;\r
238                 }\r
239             }\r
240         }\r
241 \r
242         public int MissionAntiSubmarine => AntiSubmarine - AllSlot.Sum(item =>\r
243         {\r
244             switch (item.Spec.Type)\r
245             {\r
246                 case 10: // 水偵\r
247                 case 11: // 水爆\r
248                 case 41: // 大艇\r
249                     return item.Spec.AntiSubmarine;\r
250                 default:\r
251                     return 0;\r
252             }\r
253         });\r
254 \r
255         private bool HaveSonar => Slot.Any(item => item.Spec.IsSonar);\r
256 \r
257         public double NightBattlePower\r
258         {\r
259             get\r
260             {\r
261                 if (!Spec.IsAircraftCarrier)\r
262                     return Firepower + Torpedo + Slot.Sum(item => item.NightBattleLevelBonus);\r
263 \r
264                 if (Slot.Any(item => item.Spec.IconType == 45 || item.Spec.IconType == 46) && // 夜戦か夜攻\r
265                     (Spec.Id == 545 || // Saratoga Mk.II\r
266                      Slot.Any(item => item.Spec.Id == 258 || item.Spec.Id == 259))) // 夜間作戦航空要員\r
267                 {\r
268                     return Firepower + Slot.Zip(OnSlot, (item, onSlot) =>\r
269                     {\r
270                         double a, b;\r
271                         var spec = item.Spec;\r
272                         switch (spec.Id)\r
273                         {\r
274                             case 154: // 零戦62型(爆戦/岩井隊)\r
275                             case 242: // Swordfish\r
276                             case 243: // Swordfish Mk.II(熟練)\r
277                             case 244: // Swordfish Mk.III(熟練)\r
278                                 a = 0.0;\r
279                                 b = 0.3;\r
280                                 break;\r
281                             case 254: // F6F-3N\r
282                             case 255: // F6F-5N\r
283                             case 257: // TBD-3D\r
284                                 a = 3.0;\r
285                                 b = 0.45;\r
286                                 break;\r
287                             default:\r
288                                 return -spec.Firepower;\r
289                         }\r
290                         return spec.Torpedo + a * onSlot +\r
291                                b * (spec.Firepower + spec.Torpedo + spec.Bomber + spec.AntiSubmarine) *\r
292                                Sqrt(onSlot) + Sqrt(item.Level);\r
293                     }).Sum();\r
294                 }\r
295                 switch (Spec.Id)\r
296                 {\r
297                     case 353: // Graf Zeppelin改\r
298                     case 432: // Graf Zeppelin\r
299                     case 433: // Saratoga\r
300                         break;\r
301                     case 393: // Ark Royal改\r
302                     case 515: // Ark Royal\r
303                         if (Slot.Any(item => new[] {242, 243, 244}.Contains(item.Spec.Id)))\r
304                             break;\r
305                         return 0;\r
306                     default:\r
307                         return 0;\r
308                 }\r
309                 return Firepower + Torpedo + Slot.Sum(item => item.NightBattleLevelBonus);\r
310             }\r
311         }\r
312 \r
313         public int PreparedDamageControl =>\r
314             DamageLevel != Damage.Badly\r
315                 ? -1\r
316                 : SlotEx.Spec.Id == 42 || SlotEx.Spec.Id == 43\r
317                     ? SlotEx.Spec.Id\r
318                     : Slot.FirstOrDefault(item => item.Spec.Id == 42 || item.Spec.Id == 43)?.Spec.Id ?? -1;\r
319 \r
320         public double TransportPoint\r
321             => Spec.TransportPoint + AllSlot.Sum(item => item.Spec.TransportPoint);\r
322 \r
323         public int EffectiveAntiAirForShip\r
324         {\r
325             get\r
326             {\r
327                 if (AllSlot.All(item => item.Empty || item.Unimplemented))\r
328                     return AntiAir;\r
329                 var vanilla = AntiAir - AllSlot.Sum(item => item.Spec.AntiAir);\r
330                 var x = vanilla + AllSlot.Sum(item => item.EffectiveAntiAirForShip);\r
331                 return (int)(x / 2) * 2;\r
332             }\r
333         }\r
334 \r
335         public int EffectiveAntiAirForFleet => (int)AllSlot.Sum(item => item.EffectiveAntiAirForFleet);\r
336 \r
337         public int EffectiveFuelMax => Max((int)(Spec.FuelMax * (Level >= 100 ? 0.85 : 1.0)), 1);\r
338 \r
339         public int EffectiveBullMax => Max((int)(Spec.BullMax * (Level >= 100 ? 0.85 : 1.0)), 1);\r
340 \r
341         public object Clone()\r
342         {\r
343             var r = (ShipStatus)MemberwiseClone();\r
344             r.Slot = r.Slot.ToArray(); // 戦闘中のダメコンの消費が見えないように複製する\r
345             return r;\r
346         }\r
347     }\r
348 }