OSDN Git Service

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