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