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