OSDN Git Service

艦娘の装備が任務で消滅したときに例外が発生するのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / ShipInfo.cs
1 // Copyright (C) 2013, 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 static System.Math;\r
19 \r
20 namespace KancolleSniffer\r
21 {\r
22     public class ShipStatus : ICloneable\r
23     {\r
24         public int Id { get; set; }\r
25         public int Fleet { get; set; }\r
26         public ShipSpec Spec { get; set; }\r
27 \r
28         public string Name => Spec.Name;\r
29 \r
30         public int Level { get; set; }\r
31         public int ExpToNext { get; set; }\r
32         public int MaxHp { get; set; }\r
33         public int NowHp { get; set; }\r
34         public int Cond { get; set; }\r
35         public int Fuel { get; set; }\r
36         public int Bull { get; set; }\r
37         public int[] OnSlot { get; set; }\r
38         public ItemStatus[] Slot { get; set; }\r
39         public ItemStatus SlotEx { get; set; }\r
40         public int LoS { get; set; }\r
41         public int Firepower { get; set; }\r
42         public int Torpedo { get; set; }\r
43         public int AntiSubmarine { get; set; }\r
44         public int Lucky { get; set; }\r
45         public bool Locked { get; set; }\r
46         public bool Escaped { get; set; }\r
47 \r
48         public Damage DamageLevel => CalcDamage(NowHp, MaxHp);\r
49 \r
50         public int CombinedFleetType { get; set; }\r
51 \r
52         public ShipStatus()\r
53         {\r
54             Id = -1;\r
55             Spec = new ShipSpec();\r
56             OnSlot = new int[0];\r
57             Slot = new ItemStatus[0];\r
58             SlotEx = new ItemStatus();\r
59         }\r
60 \r
61         public enum Damage\r
62         {\r
63             Minor,\r
64             Small,\r
65             Half,\r
66             Badly\r
67         }\r
68 \r
69         public static Damage CalcDamage(int now, int max)\r
70         {\r
71             var ratio = max == 0 ? 1 : (double)now / max;\r
72             return ratio > 0.75 ? Damage.Minor : ratio > 0.5 ? Damage.Small : ratio > 0.25 ? Damage.Half : Damage.Badly;\r
73         }\r
74 \r
75         public TimeSpan RepairTime => TimeSpan.FromSeconds(CalcRepairSec(MaxHp - NowHp) + 30);\r
76 \r
77         public int CalcRepairSec(int damage) => (int)(RepairSecPerHp * damage);\r
78 \r
79         public double RepairSecPerHp\r
80         {\r
81             get\r
82             {\r
83                 var weight = Spec.RepairWeight;\r
84                 var level = Level < 12 ? Level * 10 : Level * 5 + Floor(Sqrt(Level - 11)) * 10 + 50;\r
85                 return level * weight;\r
86             }\r
87         }\r
88 \r
89         public void CalcMaterialsToRepair(out int fuel, out int steal)\r
90         {\r
91             var damage = MaxHp - NowHp;\r
92             fuel = (int)(Spec.FuelMax * 0.2 * 0.16 * damage);\r
93             steal = (int)(Spec.FuelMax * 0.2 * 0.3 * damage);\r
94         }\r
95 \r
96         public double RealFirepower\r
97         {\r
98             get\r
99             {\r
100                 if (Spec.IsSubmarine)\r
101                     return 0;\r
102                 var isRyuseiAttack = Spec.Id == 352 && // 速吸改\r
103                                      Slot.Any(item => item.Spec.Type == 8); // 艦攻装備\r
104                 var levelBonus = Slot.Sum(item => item.FirePowerLevelBonus);\r
105                 if (!Spec.IsAircraftCarrier && !isRyuseiAttack)\r
106                     return Firepower + levelBonus + CombinedFleetFirepowerBonus + 5;\r
107                 var specs = (from item in Slot where item.Spec.IsAircraft select item.Spec).ToArray();\r
108                 var torpedo = specs.Sum(s => s.Torpedo);\r
109                 var bomber = specs.Sum(s => s.Bomber);\r
110                 if (torpedo == 0 && bomber == 0)\r
111                     return 0;\r
112                 return (int)((Firepower + torpedo + levelBonus + (int)(bomber * 1.3) + CombinedFleetFirepowerBonus) * 1.5) + 55;\r
113             }\r
114         }\r
115 \r
116         private int CombinedFleetFirepowerBonus\r
117         {\r
118             get\r
119             {\r
120                 switch (CombinedFleetType)\r
121                 {\r
122                     case 0:\r
123                         return 0;\r
124                     case 1: // 機動\r
125                         return Fleet == 0 ? 2 : 10;\r
126                     case 2: // 水上\r
127                         return Fleet == 0 ? 10 : -5;\r
128                     case 3: // 輸送\r
129                         return Fleet == 0 ? -5 : 10;\r
130                     default:\r
131                         return 0;\r
132                 }\r
133             }\r
134         }\r
135 \r
136         public double RealTorpedo\r
137         {\r
138             get\r
139             {\r
140                 if (Spec.IsAircraftCarrier || Torpedo == 0)\r
141                     return 0;\r
142                 return Torpedo + Slot.Sum(item => item.TorpedoLevelBonus) + CombinedFleetTorpedoPenalty + 5;\r
143             }\r
144         }\r
145 \r
146         private int CombinedFleetTorpedoPenalty => CombinedFleetType > 0 && Fleet == 1 ? -5 : 0;\r
147 \r
148         public double RealAntiSubmarine\r
149         {\r
150             get\r
151             {\r
152                 if (!Spec.IsAntiSubmarine)\r
153                     return 0;\r
154                 // ReSharper disable once CompareOfFloatsByEqualityOperator\r
155                 if (Spec.IsAircraftCarrier && RealFirepower == 0) // 砲撃戦に参加しない\r
156                     return 0;\r
157                 var sonar = false;\r
158                 var dc = false;\r
159                 var aircraft = false;\r
160                 var all = 0.0;\r
161                 var vanilla = AntiSubmarine;\r
162                 foreach (var spec in Slot.Select(item => item.Spec))\r
163                 {\r
164                     vanilla -= spec.AntiSubmarine;\r
165                     if (spec.IsSonar)\r
166                         sonar = true;\r
167                     else if (spec.IsDepthCharge)\r
168                         dc = true;\r
169                     else if (spec.IsAircraft)\r
170                         aircraft = true;\r
171                     all += spec.RealAntiSubmarine;\r
172                 }\r
173                 if (vanilla == 0 && !aircraft) // 素対潜0で航空機なしは対潜攻撃なし\r
174                     return 0;\r
175                 var bonus = sonar && dc ? 1.15 : 1.0;\r
176                 var levelBonus = Slot.Sum(item => item.AntiSubmarineLevelBonus);\r
177                 return bonus * (Sqrt(vanilla) * 2 + all * 1.5 + levelBonus + (aircraft ? 8 : 13));\r
178             }\r
179         }\r
180 \r
181         public double NightBattlePower\r
182         {\r
183             get\r
184             {\r
185                 if (Spec.IsAircraftCarrier && Spec.Id != 353 && Spec.Id != 432) // Graf Zeppelin以外の空母\r
186                     return 0;\r
187                 return Firepower + Torpedo + Slot.Sum(item => item.NightBattleLevelBonus);\r
188             }\r
189         }\r
190 \r
191         public int PreparedDamageControl =>\r
192             (DamageLevel < Damage.Badly)\r
193                 ? -1\r
194                 : SlotEx.Spec.Id == 42 || SlotEx.Spec.Id == 43\r
195                     ? SlotEx.Spec.Id\r
196                     : Slot.FirstOrDefault(item => item.Spec.Id == 42 || item.Spec.Id == 43)?.Spec.Id ?? -1;\r
197 \r
198         public double TransportPoint\r
199             => Spec.TransportPoint + Slot.Sum(item => item.Spec.TransportPoint) + SlotEx.Spec.TransportPoint;\r
200 \r
201         public object Clone()\r
202         {\r
203             return MemberwiseClone();\r
204         }\r
205     }\r
206 \r
207     public struct ChargeStatus\r
208     {\r
209         public int Fuel { get; set; }\r
210         public int Bull { get; set; }\r
211 \r
212         public ChargeStatus(ShipStatus status) : this()\r
213         {\r
214             Fuel = CalcChargeState(status.Fuel, status.Spec.FuelMax);\r
215             Bull = CalcChargeState(status.Bull, status.Spec.BullMax);\r
216         }\r
217 \r
218         public ChargeStatus(int fuel, int bull) : this()\r
219         {\r
220             Fuel = fuel;\r
221             Bull = bull;\r
222         }\r
223 \r
224         private int CalcChargeState(int now, int full)\r
225         {\r
226             if (full == 0 || now == full)\r
227                 return 0;\r
228             var ratio = (double)now / full;\r
229             if (ratio >= 7.0 / 9)\r
230                 return 1;\r
231             if (ratio >= 3.0 / 9)\r
232                 return 2;\r
233             if (ratio > 0)\r
234                 return 3;\r
235             return 4;\r
236         }\r
237     }\r
238 \r
239     public class ShipInfo\r
240     {\r
241         public const int FleetCount = 4;\r
242         public const int MemberCount = 6;\r
243 \r
244         private readonly int[][] _decks = new int[FleetCount][];\r
245         private readonly Dictionary<int, ShipStatus> _shipInfo = new Dictionary<int, ShipStatus>();\r
246         private readonly ShipMaster _shipMaster = new ShipMaster();\r
247         private readonly ItemInfo _itemInfo;\r
248         private readonly bool[] _inMission = new bool[FleetCount];\r
249         private readonly bool[] _inSortie = new bool[FleetCount];\r
250         private int _hqLevel;\r
251         private readonly List<int> _escapedShips = new List<int>();\r
252         private int _combinedFleetType;\r
253         private readonly int[][] _presetDeck = new int[8][];\r
254 \r
255         public ShipInfo(ItemInfo itemInfo)\r
256         {\r
257             _itemInfo = itemInfo;\r
258 \r
259             for (var fleet = 0; fleet < FleetCount; fleet++)\r
260             {\r
261                 var deck = new int[MemberCount];\r
262                 for (var i = 0; i < deck.Length; i++)\r
263                     deck[i] = -1;\r
264                 _decks[fleet] = deck;\r
265             }\r
266             ClearShipInfo();\r
267         }\r
268 \r
269         public void InspectMaster(dynamic json)\r
270         {\r
271             _shipMaster.Inspect(json);\r
272         }\r
273 \r
274         public void InspectShip(dynamic json)\r
275         {\r
276             if (json.api_deck_port()) // port\r
277             {\r
278                 ClearShipInfo();\r
279                 for (var i = 0; i < FleetCount; i++)\r
280                     _inSortie[i] = false;\r
281                 InspectDeck(json.api_deck_port);\r
282                 InspectShipData(json.api_ship);\r
283                 InspectBasic(json.api_basic);\r
284                 if (json.api_combined_flag())\r
285                     _combinedFleetType = (int)json.api_combined_flag;\r
286                 _itemInfo.NowShips = ((object[])json.api_ship).Length;\r
287             }\r
288             else if (json.api_data()) // ship2\r
289             {\r
290                 ClearShipInfo();\r
291                 InspectDeck(json.api_data_deck);\r
292                 InspectShipData(json.api_data);\r
293                 _itemInfo.NowShips = ((object[])json.api_data).Length;\r
294             }\r
295             else if (json.api_ship_data()) // ship3とship_deck\r
296             {\r
297                 // 一隻分のデータしか来ないことがあるので艦娘数を数えない\r
298                 InspectDeck(json.api_deck_data);\r
299                 InspectShipData(json.api_ship_data);\r
300             }\r
301             else if (json.api_ship()) // getship\r
302             {\r
303                 InspectShipData(new[] {json.api_ship});\r
304             }\r
305         }\r
306 \r
307         private void ClearShipInfo()\r
308         {\r
309             _shipInfo.Clear();\r
310             _shipInfo[-1] = new ShipStatus();\r
311         }\r
312 \r
313         public void InspectDeck(dynamic json)\r
314         {\r
315             foreach (var entry in json)\r
316             {\r
317                 var fleet = (int)entry.api_id - 1;\r
318                 var deck = _decks[fleet];\r
319                 for (var i = 0; i < deck.Length; i++)\r
320                     deck[i] = (int)entry.api_ship[i];\r
321                 _inMission[fleet] = (int)entry.api_mission[0] != 0;\r
322             }\r
323         }\r
324 \r
325         private void InspectShipData(dynamic json)\r
326         {\r
327             foreach (var entry in json)\r
328             {\r
329                 _shipInfo[(int)entry.api_id] = new ShipStatus\r
330                 {\r
331                     Id = (int)entry.api_id,\r
332                     Spec = _shipMaster[(int)entry.api_ship_id],\r
333                     Level = (int)entry.api_lv,\r
334                     ExpToNext = (int)entry.api_exp[1],\r
335                     MaxHp = (int)entry.api_maxhp,\r
336                     NowHp = (int)entry.api_nowhp,\r
337                     Cond = (int)entry.api_cond,\r
338                     Fuel = (int)entry.api_fuel,\r
339                     Bull = (int)entry.api_bull,\r
340                     OnSlot = (int[])entry.api_onslot,\r
341                     Slot = ((int[])entry.api_slot).Select(id => new ItemStatus(id)).ToArray(),\r
342                     SlotEx = entry.api_slot_ex() ? new ItemStatus((int)entry.api_slot_ex) : new ItemStatus(),\r
343                     LoS = (int)entry.api_sakuteki[0],\r
344                     Firepower = (int)entry.api_karyoku[0],\r
345                     Torpedo = (int)entry.api_raisou[0],\r
346                     AntiSubmarine = (int)entry.api_taisen[0],\r
347                     Lucky = (int)entry.api_lucky[0],\r
348                     Locked = entry.api_locked() && entry.api_locked == 1\r
349                 };\r
350             }\r
351         }\r
352 \r
353         private void InspectBasic(dynamic json)\r
354         {\r
355             _hqLevel = (int)json.api_level;\r
356         }\r
357 \r
358         public void InspectCharge(dynamic json)\r
359         {\r
360             foreach (var entry in json.api_ship)\r
361             {\r
362                 var status = _shipInfo[(int)entry.api_id];\r
363                 status.Bull = (int)entry.api_bull;\r
364                 status.Fuel = (int)entry.api_fuel;\r
365                 status.OnSlot = (from num in (dynamic[])entry.api_onslot select (int)num).ToArray();\r
366             }\r
367         }\r
368 \r
369         public void InspectChange(string request)\r
370         {\r
371             var values = HttpUtility.ParseQueryString(request);\r
372             var fleet = int.Parse(values["api_id"]) - 1;\r
373             var idx = int.Parse(values["api_ship_idx"]);\r
374             var ship = int.Parse(values["api_ship_id"]);\r
375             if (idx == -1)\r
376             {\r
377                 var deck = _decks[fleet];\r
378                 for (var i = 1; i < deck.Length; i++)\r
379                     deck[i] = -1;\r
380                 return;\r
381             }\r
382             if (ship == -1)\r
383             {\r
384                 WithdrowShip(fleet, idx);\r
385                 return;\r
386             }\r
387             int oi;\r
388             var of = FindFleet(ship, out oi);\r
389             var orig = _decks[fleet][idx];\r
390             _decks[fleet][idx] = ship;\r
391             if (of == -1)\r
392                 return;\r
393             // 入れ替えの場合\r
394             if ((_decks[of][oi] = orig) == -1)\r
395                 WithdrowShip(of, oi);\r
396         }\r
397 \r
398         private int FindFleet(int ship, out int idx)\r
399         {\r
400             for (var f = 0; f < _decks.Length; f++)\r
401             {\r
402                 idx = Array.FindIndex(_decks[f], id => id == ship);\r
403                 if (idx < 0)\r
404                     continue;\r
405                 return f;\r
406             }\r
407             idx = -1;\r
408             return -1;\r
409         }\r
410 \r
411         private void WithdrowShip(int fleet, int idx)\r
412         {\r
413             var deck = _decks[fleet];\r
414             var j = idx;\r
415             for (var i = idx + 1; i < deck.Length; i++)\r
416             {\r
417                 if (deck[i] != -1)\r
418                     deck[j++] = deck[i];\r
419             }\r
420             for (; j < deck.Length; j++)\r
421                 deck[j] = -1;\r
422         }\r
423 \r
424         public void InspectPowerup(string request, dynamic json)\r
425         {\r
426             var values = HttpUtility.ParseQueryString(request);\r
427             var ships = values["api_id_items"].Split(',');\r
428             _itemInfo.NowShips -= ships.Length;\r
429             _itemInfo.DeleteItems(ships.SelectMany(s => _shipInfo[int.Parse(s)].Slot).ToArray());\r
430             foreach (var ship in ships)\r
431                 _shipInfo.Remove(int.Parse(ship));\r
432             InspectDeck(json.api_deck);\r
433             InspectShip(json.api_ship);\r
434         }\r
435 \r
436         public void InspectSlotExchange(string request, dynamic json)\r
437         {\r
438             var values = HttpUtility.ParseQueryString(request);\r
439             var ship = int.Parse(values["api_id"]);\r
440             _shipInfo[ship].Slot = ((int[])json.api_slot).Select(id => new ItemStatus(id)).ToArray();\r
441         }\r
442 \r
443         public void InspectSlotDeprive(dynamic json)\r
444         {\r
445             InspectShipData(new[] {json.api_ship_data.api_set_ship, json.api_ship_data.api_unset_ship});\r
446         }\r
447 \r
448         public void InspectDestroyShip(string request, dynamic json)\r
449         {\r
450             var values = HttpUtility.ParseQueryString(request);\r
451             var ship = int.Parse(values["api_ship_id"]);\r
452             _itemInfo.NowShips--;\r
453             _itemInfo.DeleteItems(_shipInfo[ship].Slot);\r
454             int oi;\r
455             var of = FindFleet(ship, out oi);\r
456             if (of != -1)\r
457                 WithdrowShip(of, oi);\r
458             _shipInfo.Remove(ship);\r
459         }\r
460 \r
461         public void InspectPresetDeck(dynamic json)\r
462         {\r
463             foreach (KeyValuePair<string, dynamic> entry in json.api_deck)\r
464                 InspectPresetRegister(entry.Value);\r
465         }\r
466 \r
467         public void InspectPresetRegister(dynamic json)\r
468         {\r
469             var no = (int)json.api_preset_no - 1;\r
470             _presetDeck[no] = json.api_ship;\r
471         }\r
472 \r
473         public void InspectPresetDelete(string request)\r
474         {\r
475             var values = HttpUtility.ParseQueryString(request);\r
476             _presetDeck[int.Parse(values["api_preset_no"]) - 1] = null;\r
477         }\r
478 \r
479         public void InspectCombined(string request)\r
480         {\r
481             var values = HttpUtility.ParseQueryString(request);\r
482             _combinedFleetType = int.Parse(values["api_combined_type"]);\r
483         }\r
484 \r
485         public int[][] PresetDeck => _presetDeck;\r
486 \r
487         public void InspectMapStart(string request)\r
488         {\r
489             var values = HttpUtility.ParseQueryString(request);\r
490             var fleet = int.Parse(values["api_deck_id"]) - 1;\r
491             if (_combinedFleetType == 0 || fleet > 1)\r
492             {\r
493                 _inSortie[fleet] = true;\r
494             }\r
495             else\r
496             {\r
497                 _inSortie[0] = _inSortie[1] = true;\r
498             }\r
499             SetBadlyDamagedShips();\r
500         }\r
501 \r
502         public void RepairShip(int id)\r
503         {\r
504             var s = _shipInfo[id];\r
505             s.NowHp = s.MaxHp;\r
506             s.Cond = Max(40, s.Cond);\r
507         }\r
508 \r
509         public ShipStatus[] GetShipStatuses(int fleet)\r
510         {\r
511             return _decks[fleet].Where(id => id != -1).Select(GetStatus).ToArray();\r
512         }\r
513 \r
514         public int[] GetDeck(int fleet) => _decks[fleet];\r
515 \r
516         public ShipStatus GetStatus(int id)\r
517         {\r
518             ShipStatus s;\r
519             if (!_shipInfo.TryGetValue(id, out s))\r
520                 return new ShipStatus();\r
521             s.Slot = s.Slot.Select(item => _itemInfo.GetStatus(item.Id)).ToArray();\r
522             s.SlotEx = _itemInfo.GetStatus(s.SlotEx.Id);\r
523             s.Escaped = _escapedShips.Contains(id);\r
524             int idx;\r
525             s.Fleet = FindFleet(s.Id, out idx);\r
526             s.CombinedFleetType = s.Fleet < 2 ? _combinedFleetType : 0;\r
527             return s;\r
528         }\r
529 \r
530         public ShipSpec GetSpec(int id) => _shipMaster[id];\r
531 \r
532         public bool InMission(int fleet) => _inMission[fleet];\r
533 \r
534         public bool InSortie(int fleet) => _inSortie[fleet];\r
535 \r
536         public int CombinedFleetType => _combinedFleetType;\r
537 \r
538         public ShipStatus[] ShipList => _shipInfo.Keys.Where(id => id != -1).Select(GetStatus).ToArray();\r
539 \r
540         public ChargeStatus[] ChargeStatuses\r
541             => (from deck in _decks\r
542                 let flag = new ChargeStatus(_shipInfo[deck[0]])\r
543                 let others = (from id in deck.Skip(1)\r
544                     select new ChargeStatus(_shipInfo[id]))\r
545                     .Aggregate(\r
546                         (result, next) =>\r
547                             new ChargeStatus(Max(result.Fuel, next.Fuel), Max(result.Bull, next.Bull)))\r
548                 select new ChargeStatus(flag.Fuel != 0 ? flag.Fuel : others.Fuel + 5,\r
549                     flag.Bull != 0 ? flag.Bull : others.Bull + 5)).ToArray();\r
550 \r
551         public int[] GetFighterPower(int fleet)\r
552             => GetShipStatuses(fleet).Where(ship => !ship.Escaped).SelectMany(ship =>\r
553                 ship.Slot.Zip(ship.OnSlot, (slot, onslot) =>\r
554                 {\r
555                     if (!slot.Spec.CanAirCombat || onslot == 0)\r
556                         return new[] {0, 0};\r
557                     var unskilled = (slot.Spec.AntiAir + slot.FighterPowerLevelBonus) * Sqrt(onslot);\r
558                     return new[] {(int)(unskilled + slot.AlvBonus[0]), (int)(unskilled + slot.AlvBonus[1])};\r
559                 }))\r
560                 .Aggregate(new[] {0, 0}, (prev, fp) => new[] {prev[0] + fp[0], prev[1] + fp[1]});\r
561 \r
562         public double GetContactTriggerRate(int fleet)\r
563             => GetShipStatuses(fleet).Where(ship => !ship.Escaped).SelectMany(ship =>\r
564                 ship.Slot.Zip(ship.OnSlot, (slot, onslot) =>\r
565                     slot.Spec.ContactTriggerRate * slot.Spec.LoS * Sqrt(onslot))).Sum();\r
566 \r
567         public ShipStatus[] GetDamagedShipList(DockInfo dockInfo)\r
568             => (from s in ShipList\r
569                 where s.NowHp < s.MaxHp && !dockInfo.InNDock(s.Id)\r
570                 select s).OrderByDescending(s => s.RepairTime).ToArray();\r
571 \r
572 \r
573         public double GetLineOfSights(int fleet)\r
574         {\r
575             var result = 0.0;\r
576             var emptyBonus = 6;\r
577             foreach (var s in GetShipStatuses(fleet))\r
578             {\r
579                 emptyBonus--;\r
580                 var itemLoS = 0;\r
581                 foreach (var item in s.Slot)\r
582                 {\r
583                     var spec = item.Spec;\r
584                     itemLoS += spec.LoS;\r
585                     result += (spec.LoS + item.LoSLevelBonus) * spec.LoSScaleFactor;\r
586                 }\r
587                 result += Sqrt(s.LoS - itemLoS);\r
588             }\r
589             return result > 0 ? result - Ceiling(_hqLevel * 0.4) + emptyBonus * 2 : 0.0;\r
590         }\r
591 \r
592         public string[] BadlyDamagedShips { get; private set; } = new string[0];\r
593 \r
594         public void SetBadlyDamagedShips()\r
595         {\r
596             BadlyDamagedShips =\r
597                 _inSortie.SelectMany((sortie, i) => sortie ? GetShipStatuses(i) : new ShipStatus[0])\r
598                     .Where(s => !s.Escaped && s.DamageLevel == ShipStatus.Damage.Badly)\r
599                     .Select(s => s.Name)\r
600                     .ToArray();\r
601         }\r
602 \r
603         public void ClearBadlyDamagedShips()\r
604         {\r
605             BadlyDamagedShips = new string[0];\r
606         }\r
607 \r
608         public void SetEscapedShips(List<int> ships)\r
609         {\r
610             _escapedShips.AddRange(ships);\r
611         }\r
612 \r
613         public void ClearEscapedShips()\r
614         {\r
615             _escapedShips.Clear();\r
616         }\r
617     }\r
618 }