OSDN Git Service

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