OSDN Git Service

連合艦隊同士の航空戦に第二艦隊が参加することに対応する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / BattleInfo.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.Collections.Generic;\r
16 using System.Linq;\r
17 using static System.Math;\r
18 \r
19 namespace KancolleSniffer\r
20 {\r
21     public enum BattleResultRank\r
22     {\r
23         P,\r
24         S,\r
25         A,\r
26         B,\r
27         C,\r
28         D,\r
29         E\r
30     }\r
31 \r
32     public enum BattleState\r
33     {\r
34         None,\r
35         Day,\r
36         Night,\r
37         Result\r
38     }\r
39 \r
40     public class BattleInfo\r
41     {\r
42         private readonly ShipInfo _shipInfo;\r
43         private readonly ItemInfo _itemInfo;\r
44         private int _fleet;\r
45         private Record[] _friend;\r
46         private Record[] _guard;\r
47         private int[] _enemyHp;\r
48         private int[] _enemyGuardHp;\r
49         private int[] _enemyStartHp;\r
50         private int[] _enemyGuardStartHp;\r
51         private readonly List<int> _escapingShips = new List<int>();\r
52         private int _flagshipRecoveryType;\r
53         private bool _lastCell;\r
54 \r
55         public BattleState BattleState { get; set; }\r
56         public string Formation { get; private set; }\r
57         public string EnemyFighterPower { get; private set; }\r
58         public int AirControlLevel { get; private set; }\r
59         public BattleResultRank ResultRank { get; private set; }\r
60         public ShipStatus[] EnemyResultStatus { get; private set; }\r
61         public bool EnemyIsCombined => EnemyResultStatus.Length > 6;\r
62         public List<AirBattleResult> AirBattleResults { get; } = new List<AirBattleResult>();\r
63 \r
64         public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
65         {\r
66             _shipInfo = shipInfo;\r
67             _itemInfo = itemInfo;\r
68         }\r
69 \r
70         public void InspectBattle(dynamic json, string url)\r
71         {\r
72             Formation = FormationName(json);\r
73             AirControlLevel = CheckAirControlLevel(json);\r
74             ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
75             SetupResult(json);\r
76             EnemyFighterPower = CalcEnemyFighterPower(json);\r
77             if (IsNightBattle(json))\r
78             {\r
79                 BattleState = BattleState.Night;\r
80                 CalcHougekiDamage(json.api_hougeki,\r
81                     _guard.Length > 0 ? _guard : _friend,\r
82                     json.api_active_deck() && json.api_active_deck[1] != 1 ? _enemyGuardHp : _enemyHp);\r
83             }\r
84             else\r
85             {\r
86                 BattleState = BattleState.Day;\r
87                 CalcDamage(json, url.EndsWith("battle_water"));\r
88             }\r
89             ClearEnemyOverKill();\r
90             ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
91         }\r
92 \r
93         private void ClearEnemyOverKill()\r
94         {\r
95             _enemyHp = _enemyHp.Select(hp => hp < 0 ? 0 : hp).ToArray();\r
96             _enemyGuardHp = _enemyGuardHp.Select(hp => hp < 0 ? 0 : hp).ToArray();\r
97         }\r
98 \r
99         public void InspectMapNext(string request)\r
100         {\r
101             var type = HttpUtility.ParseQueryString(request)["api_recovery_type"];\r
102             if (type == null)\r
103                 return;\r
104             _flagshipRecoveryType = int.Parse(type);\r
105         }\r
106 \r
107         private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
108 \r
109         public static int DeckId(dynamic json)\r
110         {\r
111             if (json.api_dock_id()) // 昼戦はtypoしている\r
112                 return (int)json.api_dock_id - 1;\r
113             if (json.api_deck_id is string) // 通常の夜戦と連合艦隊(味方のみ)では文字列\r
114                 return int.Parse(json.api_deck_id) - 1;\r
115             return (int)json.api_deck_id - 1;\r
116         }\r
117 \r
118         private string FormationName(dynamic json)\r
119         {\r
120             if (!json.api_formation()) // 演習の夜戦\r
121                 return "";\r
122             switch ((int)json.api_formation[2])\r
123             {\r
124                 case 1:\r
125                     return "同航戦";\r
126                 case 2:\r
127                     return "反航戦";\r
128                 case 3:\r
129                     return "T字有利";\r
130                 case 4:\r
131                     return "T字不利";\r
132             }\r
133             return "";\r
134         }\r
135 \r
136         private void SetupResult(dynamic json)\r
137         {\r
138             if (_friend != null)\r
139                 return;\r
140             var nowhps = (int[])json.api_nowhps;\r
141             _fleet = DeckId(json);\r
142             var fstats = _shipInfo.GetShipStatuses(_fleet);\r
143             FlagshipRecovery(fstats[0]);\r
144             _friend = Record.Setup(fstats);\r
145             _enemyHp = nowhps.Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
146             _enemyStartHp = (int[])_enemyHp.Clone();\r
147             EnemyResultStatus =\r
148             (from id in\r
149                 json.api_ship_ke_combined()\r
150                     ? ((int[])json.api_ship_ke).Skip(1).Concat(((int[])json.api_ship_ke_combined).Skip(1))\r
151                     : ((int[])json.api_ship_ke).Skip(1)\r
152                 select new ShipStatus {Id = id, Spec = _shipInfo.GetSpec(id)}).ToArray();\r
153             _guard = new Record[0];\r
154             _enemyGuardHp = new int[0];\r
155             _enemyGuardStartHp = new int[0];\r
156             if (!json.api_nowhps_combined())\r
157                 return;\r
158             var combined = (int[])json.api_nowhps_combined;\r
159             if (combined[1] != -1) // 味方が連合艦隊\r
160                 _guard = Record.Setup(_shipInfo.GetShipStatuses(1));\r
161             if (combined.Length > 7) // 敵が連合艦隊\r
162             {\r
163                 _enemyGuardHp =\r
164                     ((int[])json.api_nowhps_combined).Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
165                 _enemyGuardStartHp = (int[])_enemyGuardHp.Clone();\r
166             }\r
167         }\r
168 \r
169         private void FlagshipRecovery(ShipStatus flagship)\r
170         {\r
171             switch (_flagshipRecoveryType)\r
172             {\r
173                 case 0:\r
174                     return;\r
175                 case 1:\r
176                     flagship.NowHp = flagship.MaxHp / 2;\r
177                     ConsumeSlotItem(flagship, 42); // ダメコン\r
178                     break;\r
179                 case 2:\r
180                     flagship.NowHp = flagship.MaxHp;\r
181                     ConsumeSlotItem(flagship, 43); // 女神\r
182                     break;\r
183             }\r
184             if (_flagshipRecoveryType != 0)\r
185                 _shipInfo.SetBadlyDamagedShips();\r
186             _flagshipRecoveryType = 0;\r
187         }\r
188 \r
189         private static void ConsumeSlotItem(ShipStatus ship, int id)\r
190         {\r
191             if (ship.SlotEx.Spec.Id == id)\r
192             {\r
193                 ship.SlotEx = new ItemStatus();\r
194                 return;\r
195             }\r
196             for (var i = 0; i < ship.Slot.Length; i++)\r
197             {\r
198                 if (ship.Slot[i].Spec.Id == id)\r
199                 {\r
200                     ship.Slot[i] = new ItemStatus();\r
201                     break;\r
202                 }\r
203             }\r
204         }\r
205 \r
206         public void CleanupResult()\r
207         {\r
208             _friend = null;\r
209             _lastCell = false;\r
210         }\r
211 \r
212         private int CheckAirControlLevel(dynamic json)\r
213         {\r
214             if (!json.api_kouku())\r
215                 return -1;\r
216             var stage1 = json.api_kouku.api_stage1;\r
217             if (stage1 == null)\r
218                 return -1;\r
219             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
220                 return -1;\r
221             return (int)stage1.api_disp_seiku;\r
222         }\r
223 \r
224         private string CalcEnemyFighterPower(dynamic json)\r
225         {\r
226             var missing = "";\r
227             var ships = ((int[])json.api_ship_ke).Skip(1);\r
228             if (json.api_ship_ke_combined() && _guard.Length > 0)\r
229                 ships = ships.Concat(((int[])json.api_ship_ke_combined).Skip(1));\r
230             var maxEq = ships.SelectMany(id =>\r
231             {\r
232                 var r = _shipInfo.GetSpec(id).MaxEq;\r
233                 if (r != null)\r
234                     return r;\r
235                 missing = "+";\r
236                 return new int[5];\r
237             });\r
238             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
239             if (json.api_eSlot_combined() && _guard.Length > 0)\r
240                 equips = equips.Concat(((int[][])json.api_eSlot_combined).SelectMany(x => x));\r
241             return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
242                        let spec = _itemInfo.GetSpecByItemId(slot.id)\r
243                        where spec.CanAirCombat\r
244                        select (int)Floor(spec.AntiAir * Sqrt(slot.max))).DefaultIfEmpty()\r
245                    .Sum() + missing;\r
246         }\r
247 \r
248         private void CalcDamage(dynamic json, bool surfaceFleet = false)\r
249         {\r
250             AirBattleResults.Clear();\r
251             var fc = _guard.Length > 0;\r
252             var ec = _enemyGuardHp.Length > 0;\r
253             var both = fc && ec;\r
254             if (json.api_air_base_injection())\r
255             {\r
256                 AddAirBattleResult(json.api_air_base_injection, "AB噴式");\r
257                 CalcKoukuDamage(json.api_air_base_injection);\r
258             }\r
259             if (json.api_injection_kouku())\r
260             {\r
261                 AddAirBattleResult(json.api_injection_kouku, "噴式");\r
262                 CalcKoukuDamage(json.api_injection_kouku);\r
263             }\r
264             if (json.api_air_base_attack())\r
265                 CalcAirBaseAttackDamage(json.api_air_base_attack);\r
266             if (json.api_kouku())\r
267             {\r
268                 AddAirBattleResult(json.api_kouku, "航空戦");\r
269                 CalcKoukuDamage(json.api_kouku);\r
270             }\r
271             if (json.api_kouku2()) // 航空戦2回目\r
272             {\r
273                 AddAirBattleResult(json.api_kouku2, "航空戦2");\r
274                 CalcKoukuDamage(json.api_kouku2);\r
275             }\r
276             if (!json.api_opening_atack()) // 航空戦のみ\r
277                 return;\r
278             if (json.api_support_info() && json.api_support_info != null)\r
279                 CalcSupportDamage(json.api_support_info);\r
280             if (json.api_opening_taisen() && json.api_opening_taisen != null)\r
281             {\r
282                 if (json.api_opening_taisen.api_at_eflag())\r
283                 {\r
284                     CalcCombinedHougekiDamage(json.api_opening_taisen, _friend, _guard, _enemyHp, _enemyGuardHp);\r
285                 }\r
286                 else\r
287                 {\r
288                     CalcHougekiDamage(json.api_opening_taisen,\r
289                         fc ? _guard : _friend, // 先制対潜攻撃の対象は護衛\r
290                         _enemyHp);\r
291                 }\r
292             }\r
293             if (json.api_opening_atack != null)\r
294             {\r
295                 if (both)\r
296                 {\r
297                     CalcSimpleDamage(json.api_opening_atack, _friend, _guard, _enemyHp, _enemyGuardHp);\r
298                 }\r
299                 else\r
300                 {\r
301                     CalcSimpleDamage(json.api_opening_atack,\r
302                         fc ? _guard : _friend, // 雷撃の対象は護衛\r
303                         _enemyHp, _enemyGuardHp);\r
304                 }\r
305             }\r
306             if (json.api_hougeki1() && json.api_hougeki1 != null)\r
307             {\r
308                 if (json.api_hougeki1.api_at_eflag())\r
309                 {\r
310                     CalcCombinedHougekiDamage(json.api_hougeki1, _friend, _guard, _enemyHp, _enemyGuardHp);\r
311                 }\r
312                 else\r
313                 {\r
314                     CalcHougekiDamage(json.api_hougeki1,\r
315                         fc && !surfaceFleet ? _guard : _friend, // 空母機動部隊は一巡目が護衛\r
316                         ec ? _enemyGuardHp : _enemyHp); // 敵連合艦隊は一巡目が護衛\r
317                 }\r
318             }\r
319             if (json.api_hougeki2() && json.api_hougeki2 != null)\r
320             {\r
321                 if (json.api_hougeki2.api_at_eflag())\r
322                 {\r
323                     CalcCombinedHougekiDamage(json.api_hougeki2, _friend, _guard, _enemyHp, _enemyGuardHp);\r
324                 }\r
325                 else\r
326                 {\r
327                     CalcHougekiDamage(json.api_hougeki2, _friend, _enemyHp);\r
328                 }\r
329             }\r
330             if (json.api_hougeki3() && json.api_hougeki3 != null)\r
331             {\r
332                 if (json.api_hougeki3.api_at_eflag())\r
333                 {\r
334                     CalcCombinedHougekiDamage(json.api_hougeki3, _friend, _guard, _enemyHp, _enemyGuardHp);\r
335                 }\r
336                 else\r
337                 {\r
338                     CalcHougekiDamage(json.api_hougeki3,\r
339                         fc && surfaceFleet ? _guard : _friend, // 水上打撃部隊は三順目が護衛\r
340                         _enemyHp);\r
341                 }\r
342             }\r
343             if (json.api_raigeki() && json.api_raigeki != null)\r
344             {\r
345                 if (both)\r
346                 {\r
347                     CalcSimpleDamage(json.api_raigeki, _friend, _guard, _enemyHp, _enemyGuardHp);\r
348                 }\r
349                 else\r
350                 {\r
351                     CalcSimpleDamage(json.api_raigeki,\r
352                         fc ? _guard : _friend, // 雷撃の対象は護衛\r
353                         _enemyHp, _enemyGuardHp);\r
354                 }\r
355             }\r
356         }\r
357 \r
358         private void CalcSupportDamage(dynamic json)\r
359         {\r
360             if (json.api_support_hourai != null)\r
361             {\r
362                 CalcSimpleDamage(json.api_support_hourai.api_damage, _enemyHp, _enemyGuardHp);\r
363             }\r
364             else if (json.api_support_airatack != null)\r
365             {\r
366                 CalcSimpleDamage(json.api_support_airatack.api_stage3.api_edam, _enemyHp, _enemyGuardHp);\r
367             }\r
368         }\r
369 \r
370         private void CalcAirBaseAttackDamage(dynamic json)\r
371         {\r
372             var i = 1;\r
373             foreach (var entry in json)\r
374             {\r
375                 AddAirBattleResult(entry, "基地" + i++);\r
376                 CalcKoukuDamage(entry);\r
377             }\r
378         }\r
379 \r
380         private void AddAirBattleResult(dynamic json, string phaseName)\r
381         {\r
382             var stage1 = json.api_stage1;\r
383             if (stage1 == null || (stage1.api_f_count == 0 && stage1.api_e_count == 0))\r
384                 return;\r
385             AirBattleResults.Add(new AirBattleResult\r
386             {\r
387                 PhaseName = phaseName,\r
388                 AirControlLevel = json.api_stage1.api_disp_seiku() ? (int)json.api_stage1.api_disp_seiku : 0,\r
389                 Stage1 = new AirBattleResult.StageResult\r
390                 {\r
391                     FriendCount = (int)json.api_stage1.api_f_count,\r
392                     FriendLost = (int)json.api_stage1.api_f_lostcount,\r
393                     EnemyCount = (int)json.api_stage1.api_e_count,\r
394                     EnemyLost = (int)json.api_stage1.api_e_lostcount\r
395                 },\r
396                 Stage2 = json.api_stage2 == null\r
397                     ? new AirBattleResult.StageResult\r
398                     {\r
399                         FriendCount = 0,\r
400                         FriendLost = 0,\r
401                         EnemyCount = 0,\r
402                         EnemyLost = 0\r
403                     }\r
404                     : new AirBattleResult.StageResult\r
405                     {\r
406                         FriendCount = (int)json.api_stage2.api_f_count,\r
407                         FriendLost = (int)json.api_stage2.api_f_lostcount,\r
408                         EnemyCount = (int)json.api_stage2.api_e_count,\r
409                         EnemyLost = (int)json.api_stage2.api_e_lostcount\r
410                     }\r
411             });\r
412         }\r
413 \r
414         private void CalcKoukuDamage(dynamic json)\r
415         {\r
416             if (!json.api_stage3() || json.api_stage3 == null)\r
417                 return;\r
418             CalcSimpleDamage(json.api_stage3, _friend, _enemyHp);\r
419             if (json.api_stage3_combined())\r
420                 CalcSimpleDamage(json.api_stage3_combined, _guard, _enemyGuardHp);\r
421         }\r
422 \r
423         private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy)\r
424         {\r
425             if (json.api_fdam())\r
426                 CalcSimpleDamage(json.api_fdam, friend);\r
427             if (json.api_edam())\r
428                 CalcSimpleDamage(json.api_edam, enemy);\r
429         }\r
430 \r
431         private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy, int[] enemyGuard)\r
432         {\r
433             CalcSimpleDamage(json.api_fdam, friend);\r
434             CalcSimpleDamage(json.api_edam, enemy, enemyGuard);\r
435         }\r
436 \r
437         private void CalcSimpleDamage(dynamic json, Record[] friend, Record[] guard, int[] enemy, int[] enemyGuard)\r
438         {\r
439             CalcSimpleDamage(json.api_fdam, friend, guard);\r
440             CalcSimpleDamage(json.api_edam, enemy, enemyGuard);\r
441         }\r
442 \r
443         private void CalcSimpleDamage(dynamic rawDamage, Record[] friend, Record[] guard)\r
444         {\r
445             var damage = (int[])rawDamage;\r
446             for (var i = 0; i < friend.Length; i++)\r
447                 friend[i].ApplyDamage(damage[i + 1]);\r
448             for (var i = 0; i < guard.Length; i++)\r
449                 guard[i].ApplyDamage(damage[i + 6 + 1]);\r
450         }\r
451 \r
452         private void CalcSimpleDamage(dynamic rawDamage, Record[] friend)\r
453         {\r
454             var damage = (int[])rawDamage;\r
455             for (var i = 0; i < friend.Length; i++)\r
456                 friend[i].ApplyDamage(damage[i + 1]);\r
457         }\r
458 \r
459         private void CalcSimpleDamage(dynamic rawDamage, int[] enemy, int[] enemyGuard)\r
460         {\r
461             var damage = (int[])rawDamage;\r
462             for (var i = 0; i < enemy.Length; i++)\r
463                 enemy[i] -= damage[i + 1];\r
464             for (var i = 0; i < enemyGuard.Length; i++)\r
465                 enemyGuard[i] -= damage[i + 6 + 1];\r
466         }\r
467 \r
468         private void CalcSimpleDamage(dynamic rawDamage, int[] result)\r
469         {\r
470             var damage = (int[])rawDamage;\r
471             for (var i = 0; i < result.Length; i++)\r
472                 result[i] -= damage[i + 1];\r
473         }\r
474 \r
475         private void CalcHougekiDamage(dynamic hougeki, Record[] friend, int[] enemy)\r
476         {\r
477             var targets = ((dynamic[])hougeki.api_df_list).Skip(1).SelectMany(x => (int[])x);\r
478             var damages = ((dynamic[])hougeki.api_damage).Skip(1).SelectMany(x => (int[])x);\r
479             foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
480             {\r
481                 if (hit.t == -1)\r
482                     continue;\r
483                 if (hit.t <= 6)\r
484                     friend[hit.t - 1].ApplyDamage(hit.d);\r
485                 else\r
486                     enemy[(hit.t - 1) % 6] -= hit.d;\r
487             }\r
488         }\r
489 \r
490         private void CalcCombinedHougekiDamage(dynamic hougeki, Record[] friend, Record[] guard,\r
491             int[] enemy, int[] enemyGuard)\r
492         {\r
493             var targets = ((dynamic[])hougeki.api_df_list).Skip(1).Select(x => (int[])x);\r
494             var damages = ((dynamic[])hougeki.api_damage).Skip(1).Select(x => (int[])x);\r
495             var eflags = ((int[])hougeki.api_at_eflag).Skip(1);\r
496             foreach (var turn in\r
497                 targets.Zip(damages, (t, d) => new {t, d}).Zip(eflags, (td, e) => new {e, td.t, td.d}))\r
498             {\r
499                 foreach (var hit in turn.t.Zip(turn.d, (t, d) => new {t, d}))\r
500                 {\r
501                     if (turn.e == 1)\r
502                     {\r
503                         if (hit.t <= 6)\r
504                             friend[hit.t - 1].ApplyDamage(hit.d);\r
505                         else\r
506                             guard[(hit.t - 1) % 6].ApplyDamage(hit.d);\r
507                     }\r
508                     else\r
509                     {\r
510                         if (hit.t <= 6)\r
511                             enemy[hit.t - 1] -= hit.d;\r
512                         else\r
513                             enemyGuard[(hit.t - 1) % 6] -= hit.d;\r
514                     }\r
515                 }\r
516             }\r
517         }\r
518 \r
519         public void InspectMapStart(dynamic json)\r
520         {\r
521             InspectMapNext(json);\r
522         }\r
523 \r
524         public void InspectMapNext(dynamic json)\r
525         {\r
526             _lastCell = (int)json.api_next == 0;\r
527         }\r
528 \r
529         public void InspectBattleResult(dynamic json)\r
530         {\r
531             BattleState = BattleState.Result;\r
532             ShowResult(!_lastCell);\r
533             CleanupResult();\r
534             SetEscapeShips(json);\r
535         }\r
536 \r
537         public void InspectPracticeResult(dynamic json)\r
538         {\r
539             BattleState = BattleState.Result;\r
540             ShowResult(false);\r
541             CleanupResult();\r
542         }\r
543 \r
544         private void ShowResult(bool warnDamagedShip = true)\r
545         {\r
546             if (_friend == null)\r
547                 return;\r
548             var ships = _guard.Length > 0\r
549                 ? _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray()\r
550                 : _shipInfo.GetShipStatuses(_fleet);\r
551             foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
552                 entry.now.UpdateShipStatus(entry.ship);\r
553             if (warnDamagedShip)\r
554                 _shipInfo.SetBadlyDamagedShips();\r
555             else\r
556                 _shipInfo.ClearBadlyDamagedShips();\r
557             SetEnemyResultStatus();\r
558         }\r
559 \r
560         private void SetEnemyResultStatus()\r
561         {\r
562             for (var i = 0; i < _enemyHp.Length; i++)\r
563             {\r
564                 EnemyResultStatus[i].MaxHp = _enemyStartHp[i];\r
565                 EnemyResultStatus[i].NowHp = _enemyHp[i];\r
566             }\r
567             for (var i = 0; i < _enemyGuardHp.Length; i++)\r
568             {\r
569                 EnemyResultStatus[i + 6].MaxHp = _enemyGuardStartHp[i];\r
570                 EnemyResultStatus[i + 6].NowHp = _enemyGuardHp[i];\r
571             }\r
572         }\r
573 \r
574         public void SetEscapeShips(dynamic json)\r
575         {\r
576             _escapingShips.Clear();\r
577             if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
578                 return;\r
579             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
580             _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
581             var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
582             _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
583         }\r
584 \r
585         public void CauseCombinedBattleEscape()\r
586         {\r
587             _shipInfo.SetEscapedShips(_escapingShips);\r
588             _shipInfo.SetBadlyDamagedShips();\r
589         }\r
590 \r
591         private class Record\r
592         {\r
593             private ShipStatus _status;\r
594             public int NowHp => _status.NowHp;\r
595             public bool Escaped => _status.Escaped;\r
596             public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
597             public int StartHp;\r
598 \r
599             public static Record[] Setup(ShipStatus[] ships) =>\r
600                 (from s in ships select new Record {_status = (ShipStatus)s.Clone(), StartHp = s.NowHp}).ToArray();\r
601 \r
602             public void ApplyDamage(int damage)\r
603             {\r
604                 if (_status.NowHp > damage)\r
605                 {\r
606                     _status.NowHp -= damage;\r
607                     return;\r
608                 }\r
609                 _status.NowHp = 0;\r
610                 foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))\r
611                 {\r
612                     if (item.Spec.Id == 42)\r
613                     {\r
614                         _status.NowHp = (int)(_status.MaxHp * 0.2);\r
615                         ConsumeSlotItem(_status, 42);\r
616                         break;\r
617                     }\r
618                     if (item.Spec.Id == 43)\r
619                     {\r
620                         _status.NowHp = _status.MaxHp;\r
621                         ConsumeSlotItem(_status, 43);\r
622                         break;\r
623                     }\r
624                 }\r
625             }\r
626 \r
627             public void UpdateShipStatus(ShipStatus ship)\r
628             {\r
629                 ship.NowHp = NowHp;\r
630                 ship.Slot = _status.Slot;\r
631                 ship.SlotEx = _status.SlotEx;\r
632             }\r
633         }\r
634 \r
635         private BattleResultRank CalcLdAirBattleRank()\r
636         {\r
637             var combined = _friend.Concat(_guard).ToArray();\r
638             var friendNowShips = combined.Count(r => r.NowHp > 0);\r
639             var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);\r
640             var friendSunk = combined.Count(r => r.NowHp == 0);\r
641             var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
642 \r
643             if (friendSunk == 0)\r
644             {\r
645                 if (friendGauge == 0)\r
646                     return BattleResultRank.P;\r
647                 if (friendGaugeRate < 10)\r
648                     return BattleResultRank.A;\r
649                 if (friendGaugeRate < 20)\r
650                     return BattleResultRank.B;\r
651                 if (friendGaugeRate < 50)\r
652                     return BattleResultRank.C;\r
653                 return BattleResultRank.D;\r
654             }\r
655             if (friendSunk < friendNowShips)\r
656                 return BattleResultRank.D;\r
657             return BattleResultRank.E;\r
658         }\r
659 \r
660         private BattleResultRank CalcResultRank()\r
661         {\r
662             var friend = _friend.Concat(_guard).ToArray();\r
663             var enemyHp = _enemyHp.Concat(_enemyGuardHp).ToArray();\r
664             var enemyStartHp = _enemyStartHp.Concat(_enemyGuardStartHp).ToArray();\r
665 \r
666             var friendCount = friend.Length;\r
667             var friendStartHpTotal = 0;\r
668             var friendNowHpTotal = 0;\r
669             var friendSunk = 0;\r
670             foreach (var ship in friend)\r
671             {\r
672                 if (ship.Escaped)\r
673                     continue;\r
674                 friendStartHpTotal += ship.StartHp;\r
675                 friendNowHpTotal += ship.NowHp;\r
676                 if (ship.NowHp == 0)\r
677                     friendSunk++;\r
678             }\r
679             var friendGaugeRate = (int)((double)(friendStartHpTotal - friendNowHpTotal) / friendStartHpTotal * 100);\r
680 \r
681             var enemyCount = enemyHp.Length;\r
682             var enemyStartHpTotal = enemyStartHp.Sum();\r
683             var enemyNowHpTotal = enemyHp.Sum();\r
684             var enemySunk = enemyHp.Count(hp => hp == 0);\r
685             var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);\r
686 \r
687             if (friendSunk == 0 && enemySunk == enemyCount)\r
688             {\r
689                 if (friendNowHpTotal >= friendStartHpTotal)\r
690                     return BattleResultRank.P;\r
691                 return BattleResultRank.S;\r
692             }\r
693             if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)\r
694                 return BattleResultRank.A;\r
695             if (friendSunk < enemySunk && enemyHp[0] == 0)\r
696                 return BattleResultRank.B;\r
697             if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
698                 return BattleResultRank.D;\r
699             if (enemyGaugeRate > friendGaugeRate * 2.5)\r
700                 return BattleResultRank.B;\r
701             if (enemyGaugeRate > friendGaugeRate * 0.9)\r
702                 return BattleResultRank.C;\r
703             if (friendCount > 1 && friendCount - 1 == friendSunk)\r
704                 return BattleResultRank.E;\r
705             return BattleResultRank.D;\r
706         }\r
707     }\r
708 }