OSDN Git Service

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