OSDN Git Service

海戦・ドロップ報告書に連合艦隊の護衛艦隊を出力する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / BattleInfo.cs
index ff99d4a..604e691 100644 (file)
-// Copyright (C) 2014 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
 // \r
-// This program is part of KancolleSniffer.\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
 //\r
-// KancolleSniffer is free software: you can redistribute it and/or modify\r
-// it under the terms of the GNU General Public License as published by\r
-// the Free Software Foundation, either version 3 of the License, or\r
-// (at your option) any later version.\r
+//    http://www.apache.org/licenses/LICENSE-2.0\r
 //\r
-// This program is distributed in the hope that it will be useful,\r
-// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-// GNU General Public License for more details.\r
-//\r
-// You should have received a copy of the GNU General Public License\r
-// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
 \r
-using System;\r
+using System.Collections.Generic;\r
 using System.Linq;\r
+using static System.Math;\r
 \r
 namespace KancolleSniffer\r
 {\r
+    public enum BattleResultRank\r
+    {\r
+        P,\r
+        S,\r
+        A,\r
+        B,\r
+        C,\r
+        D,\r
+        E,\r
+    }\r
+\r
+    public enum BattleState\r
+    {\r
+        None,\r
+        Day,\r
+        Night,\r
+        Result\r
+    }\r
+\r
     public class BattleInfo\r
     {\r
-        private readonly ShipMaster _shipMaster;\r
         private readonly ShipInfo _shipInfo;\r
         private readonly ItemInfo _itemInfo;\r
-        public bool InBattle { get; set; }\r
+        private int _fleet;\r
+        private Record[] _friend;\r
+        private Record[] _guard;\r
+        private int[] _enemyHp;\r
+        private int[] _enemyGuardHp;\r
+        private int[] _enemyStartHp;\r
+        private int[] _enemyGuardStartHp;\r
+        private readonly List<int> _escapingShips = new List<int>();\r
+        private int _flagshipRecoveryType;\r
+\r
+        public BattleState BattleState { get; set; }\r
         public string Formation { get; private set; }\r
-        public int DelayInFormation { get; private set; }\r
-        public int EnemyAirSuperiority { get; private set; }\r
-        public int DelayInAirSuperiority { get; private set; }\r
-\r
-        private struct Delay\r
-        {\r
-            public const int Basic = 4100;\r
-            public const int Formation = 1100;\r
-            public const int Tau = 200;\r
-            public const int SearchAirSuccess = 5700;\r
-            public const int SearchAirFailure = 4900;\r
-            public const int SearchSuccess = 5400;\r
-            public const int Submarine = 2500;\r
-            public const int Emergence = 2000;\r
-            public const int AirFight = 8700;\r
-            public const int AirFightBoth = 2700;\r
-            public const int Support = 9800;\r
-            public const int OpeningAttack = 3500;\r
-            public const int Cutin = 4900;\r
-        }\r
-\r
-        public BattleInfo(ShipMaster shipMaster, ShipInfo shipInfo, ItemInfo itemInfo)\r
-        {\r
-            _shipMaster = shipMaster;\r
+        public string EnemyFighterPower { get; private set; }\r
+        public int AirControlLevel { get; private set; }\r
+        public BattleResultRank ResultRank { get; private set; }\r
+        public ShipStatus[] EnemyResultStatus { get; private set; }\r
+        public List<AirBattleResult> AirBattleResults { get; } = new List<AirBattleResult>();\r
+\r
+\r
+        public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
+        {\r
             _shipInfo = shipInfo;\r
             _itemInfo = itemInfo;\r
         }\r
 \r
-        public void InspectBattle(dynamic json)\r
+        public void InspectBattle(dynamic json, string url)\r
         {\r
-            InBattle = true;\r
             Formation = FormationName(json);\r
-            EnemyAirSuperiority = CalcEnemyAirSuperiority(json);\r
-            SetDelay(json);\r
+            EnemyFighterPower = CalcEnemyFighterPower(json);\r
+            AirControlLevel = CheckAirControlLevel(json);\r
+            ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
+            SetupResult(json);\r
+            if (IsNightBattle(json))\r
+            {\r
+                BattleState = BattleState.Night;\r
+                CalcHougekiDamage(json.api_hougeki,\r
+                    _guard.Length > 0 ? _guard : _friend,\r
+                    json.api_active_deck() && json.api_active_deck[1] != 1 ? _enemyGuardHp : _enemyHp);\r
+            }\r
+            else\r
+            {\r
+                BattleState = BattleState.Day;\r
+                CalcDamage(json, url.EndsWith("battle_water"));\r
+            }\r
+            ClearEnemyOverKill();\r
+            ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
+        }\r
+\r
+        private void ClearEnemyOverKill()\r
+        {\r
+            _enemyHp = _enemyHp.Select(hp => hp < 0 ? 0 : hp).ToArray();\r
+            _enemyGuardHp = _enemyGuardHp.Select(hp => hp < 0 ? 0 : hp).ToArray();\r
+        }\r
+\r
+        public void InspectMapNext(string request)\r
+        {\r
+            var type = HttpUtility.ParseQueryString(request)["api_recovery_type"];\r
+            if (type == null)\r
+                return;\r
+            _flagshipRecoveryType = int.Parse(type);\r
+        }\r
+\r
+        private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
+\r
+        public static int DeckId(dynamic json)\r
+        {\r
+            if (json.api_dock_id()) // 昼戦はtypoしている\r
+                return (int)json.api_dock_id - 1;\r
+            if (json.api_deck_id is string) // 通常の夜戦と連合艦隊(味方のみ)では文字列\r
+                return int.Parse(json.api_deck_id) - 1;\r
+            return (int)json.api_deck_id - 1;\r
         }\r
 \r
         private string FormationName(dynamic json)\r
         {\r
+            if (!json.api_formation()) // 演習の夜戦\r
+                return "";\r
             switch ((int)json.api_formation[2])\r
             {\r
                 case 1:\r
@@ -79,144 +132,561 @@ namespace KancolleSniffer
             return "";\r
         }\r
 \r
-        private void SetDelay(dynamic json)\r
+        private void SetupResult(dynamic json)\r
         {\r
-            // 敵出現まで\r
-            var delay = Delay.Basic;\r
-            if ((int)json.api_formation[2] >= 3)\r
-                delay += Delay.Tau;\r
-            var subm = (SubmarineFlags)CheckSubmarine(json);\r
-            bool success;\r
-            delay += SearchDelay(json, out success) + subm.AddtionalDelay;\r
-            DelayInAirSuperiority = delay + (success ? 0 : Delay.Emergence); // 失敗すると出現が遅れる\r
-            // 敵艦隊発見以降\r
-            delay += Delay.Emergence + Delay.Formation + SupportDelay(json) + CutinDelay(json);\r
-            if (!subm.PreventAirFight)\r
-                delay += AirFightDelay(json);\r
-            if (!subm.PreventOpeningAttack)\r
-                delay += OpeningAttackDelay(json);\r
-            DelayInFormation = delay;\r
+            if (_friend != null)\r
+                return;\r
+            var nowhps = (int[])json.api_nowhps;\r
+            _fleet = DeckId(json);\r
+            var fstats = _shipInfo.GetShipStatuses(_fleet);\r
+            FlagshipRecovery(fstats[0]);\r
+            _friend = Record.Setup(fstats);\r
+            _enemyHp = nowhps.Skip(7).ToArray();\r
+            _enemyStartHp = (int[])_enemyHp.Clone();\r
+            EnemyResultStatus =\r
+            (from id in\r
+                json.api_ship_ke_combined()\r
+                    ? ((int[])json.api_ship_ke).Skip(1).Concat(((int[])json.api_ship_ke_combined).Skip(1))\r
+                    : ((int[])json.api_ship_ke).Skip(1)\r
+                select new ShipStatus {Id = id, Spec = _shipInfo.GetSpec(id)}).ToArray();\r
+            _guard = new Record[0];\r
+            _enemyGuardHp = new int[0];\r
+            _enemyGuardStartHp = new int[0];\r
+            if (!json.api_nowhps_combined())\r
+                return;\r
+            var combined = (int[])json.api_nowhps_combined;\r
+            if (combined[1] != -1) // 味方が連合艦隊\r
+                _guard = Record.Setup(_shipInfo.GetShipStatuses(1));\r
+            if (combined.Length > 7) // 敵が連合艦隊\r
+            {\r
+                _enemyGuardHp =\r
+                    ((int[])json.api_nowhps_combined).\r
+                        Skip(7).ToArray();\r
+                _enemyGuardStartHp = (int[])_enemyGuardHp.Clone();\r
+            }\r
+        }\r
+\r
+        private void FlagshipRecovery(ShipStatus flagship)\r
+        {\r
+            switch (_flagshipRecoveryType)\r
+            {\r
+                case 0:\r
+                    return;\r
+                case 1:\r
+                    flagship.NowHp = flagship.MaxHp / 2;\r
+                    ConsumeSlotItem(flagship, 42); // ダメコン\r
+                    break;\r
+                case 2:\r
+                    flagship.NowHp = flagship.MaxHp;\r
+                    ConsumeSlotItem(flagship, 43); // 女神\r
+                    break;\r
+            }\r
+            if (_flagshipRecoveryType != 0)\r
+                _shipInfo.SetBadlyDamagedShips();\r
+            _flagshipRecoveryType = 0;\r
         }\r
 \r
-        private int SearchDelay(dynamic json, out bool success)\r
+        private static void ConsumeSlotItem(ShipStatus ship, int id)\r
         {\r
-            success = false;\r
-            switch ((int)json.api_search[0])\r
+            if (ship.SlotEx.Spec.Id == id)\r
+            {\r
+                ship.SlotEx = new ItemStatus();\r
+                return;\r
+            }\r
+            for (var i = 0; i < ship.Slot.Length; i++)\r
             {\r
-                case 1: // 索敵機による索敵成功\r
-                case 2: // 索敵機未帰還あり\r
-                    success = true;\r
-                    return Delay.SearchAirSuccess;\r
-                case 3: // 索敵機未帰還\r
-                case 4: // 索敵機による索敵失敗\r
-                    return Delay.SearchAirFailure;\r
-                case 5: // 索敵力による索敵成功\r
-                    success = true;\r
-                    return Delay.SearchSuccess;\r
+                if (ship.Slot[i].Spec.Id == id)\r
+                {\r
+                    ship.Slot[i] = new ItemStatus();\r
+                    break;\r
+                }\r
             }\r
-            return 0;\r
         }\r
 \r
-        private int OpeningAttackDelay(dynamic json)\r
+        public void CleanupResult()\r
         {\r
-            return json.api_opening_flag == 1 ? Delay.OpeningAttack : 0;\r
+            _friend = null;\r
         }\r
 \r
-        private class SubmarineFlags\r
+        private int CheckAirControlLevel(dynamic json)\r
         {\r
-            public bool[] Friend;\r
-            public bool[] Enemy;\r
+            if (!json.api_kouku())\r
+                return -1;\r
+            var stage1 = json.api_kouku.api_stage1;\r
+            if (stage1 == null)\r
+                return -1;\r
+            if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
+                return -1;\r
+            return (int)stage1.api_disp_seiku;\r
+        }\r
 \r
-            public int AddtionalDelay\r
+        private string CalcEnemyFighterPower(dynamic json)\r
+        {\r
+            var missing = "";\r
+            var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id =>\r
+            {\r
+                var r = _shipInfo.GetSpec(id).MaxEq;\r
+                if (r != null)\r
+                    return r;\r
+                missing = "+";\r
+                return new int[5];\r
+            }).ToArray();\r
+            var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
+            return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
+                       let spec = _itemInfo.GetSpecByItemId(slot.id)\r
+                       where spec.CanAirCombat\r
+                       select (int)Floor(spec.AntiAir * Sqrt(slot.max))).DefaultIfEmpty().Sum() + missing;\r
+        }\r
+\r
+        private void CalcDamage(dynamic json, bool surfaceFleet = false)\r
+        {\r
+            AirBattleResults.Clear();\r
+            var fc = _guard.Length > 0;\r
+            var ec = _enemyGuardHp.Length > 0;\r
+            var both = fc && ec;\r
+            if (json.api_air_base_injection())\r
+            {\r
+                AddAirBattleResult(json.api_air_base_injection, "AB噴式");\r
+                CalcKoukuDamage(json.api_air_base_injection);\r
+            }\r
+            if (json.api_injection_kouku())\r
+            {\r
+                AddAirBattleResult(json.api_injection_kouku, "噴式");\r
+                CalcKoukuDamage(json.api_injection_kouku);\r
+            }\r
+            if (json.api_air_base_attack())\r
+                CalcAirBaseAttackDamage(json.api_air_base_attack);\r
+            if (json.api_kouku())\r
+            {\r
+                AddAirBattleResult(json.api_kouku, "航空戦");\r
+                CalcKoukuDamage(json.api_kouku);\r
+            }\r
+            if (json.api_kouku2()) // 航空戦2回目\r
             {\r
-                get { return Friend.Any(x => x) || Enemy.Any(x => x) ? Delay.Submarine : 0; }\r
-                // どちらかに潜水艦                \r
+                AddAirBattleResult(json.api_kouku2, "航空戦2");\r
+                CalcKoukuDamage(json.api_kouku2);\r
             }\r
+            if (!json.api_opening_atack()) // 航空戦のみ\r
+                return;\r
+            if (json.api_support_info() && json.api_support_info != null)\r
+                CalcSupportDamage(json.api_support_info);\r
+            if (json.api_opening_taisen() && json.api_opening_taisen != null)\r
+            {\r
+                if (json.api_opening_taisen.api_at_eflag())\r
+                {\r
+                    CalcCombinedHougekiDamage(json.api_opening_taisen, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                }\r
+                else\r
+                {\r
+                    CalcHougekiDamage(json.api_opening_taisen,\r
+                        fc ? _guard : _friend, // 先制対潜攻撃の対象は護衛\r
+                        _enemyHp);\r
+                }\r
+            }\r
+            if (json.api_opening_atack != null)\r
+            {\r
+                if (both)\r
+                {\r
+                    CalcSimpleDamage(json.api_opening_atack, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                }\r
+                else\r
+                {\r
+                    CalcSimpleDamage(json.api_opening_atack,\r
+                        fc ? _guard : _friend, // 雷撃の対象は護衛\r
+                        _enemyHp, _enemyGuardHp);\r
+                }\r
+            }\r
+            if (json.api_hougeki1() && json.api_hougeki1 != null)\r
+            {\r
+                if (json.api_hougeki1.api_at_eflag())\r
+                {\r
+                    CalcCombinedHougekiDamage(json.api_hougeki1, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                }\r
+                else\r
+                {\r
+                    CalcHougekiDamage(json.api_hougeki1,\r
+                        fc && !surfaceFleet ? _guard : _friend, // 空母機動部隊は一巡目が護衛\r
+                        ec ? _enemyGuardHp : _enemyHp); // 敵連合艦隊は一巡目が護衛\r
+                }\r
+            }\r
+            if (json.api_hougeki2() && json.api_hougeki2 != null)\r
+            {\r
+                if (json.api_hougeki2.api_at_eflag())\r
+                {\r
+                    CalcCombinedHougekiDamage(json.api_hougeki2, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                }\r
+                else\r
+                {\r
+                    CalcHougekiDamage(json.api_hougeki2, _friend, _enemyHp);\r
+                }\r
+            }\r
+            if (json.api_hougeki3() && json.api_hougeki3 != null)\r
+            {\r
+                if (json.api_hougeki3.api_at_eflag())\r
+                {\r
+                    CalcCombinedHougekiDamage(json.api_hougeki3, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                }\r
+                else\r
+                {\r
+                    CalcHougekiDamage(json.api_hougeki3,\r
+                        fc && surfaceFleet ? _guard : _friend, // 水上打撃部隊は三順目が護衛\r
+                        _enemyHp);\r
+                }\r
+            }\r
+            if (json.api_raigeki() && json.api_raigeki != null)\r
+            {\r
+                if (both)\r
+                {\r
+                    CalcSimpleDamage(json.api_raigeki, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                }\r
+                else\r
+                {\r
+                    CalcSimpleDamage(json.api_raigeki,\r
+                        fc ? _guard : _friend, // 雷撃の対象は護衛\r
+                        _enemyHp, _enemyGuardHp);\r
+                }\r
+            }\r
+        }\r
 \r
-            public bool PreventAirFight\r
+        private void CalcSupportDamage(dynamic json)\r
+        {\r
+            if (json.api_support_hourai != null)\r
+            {\r
+                CalcSimpleDamage(json.api_support_hourai.api_damage, _enemyHp, _enemyGuardHp);\r
+            }\r
+            else if (json.api_support_airatack != null)\r
             {\r
-                get { return Friend.All(x => x) || Enemy.All(x => x); } // 一方がすべて潜水艦\r
+                CalcSimpleDamage(json.api_support_airatack.api_stage3.api_edam, _enemyHp, _enemyGuardHp);\r
             }\r
+        }\r
 \r
-            public bool PreventOpeningAttack\r
+        private void CalcAirBaseAttackDamage(dynamic json)\r
+        {\r
+            var i = 1;\r
+            foreach (var entry in json)\r
             {\r
-                get { return Friend.All(x => x) && Enemy.All(x => x); } // 双方すべて潜水艦\r
+                AddAirBattleResult(entry, "基地" + i++);\r
+                CalcKoukuDamage(entry);\r
             }\r
         }\r
 \r
-        private SubmarineFlags CheckSubmarine(dynamic json)\r
+        private void AddAirBattleResult(dynamic json, string phaseName)\r
         {\r
-            return new SubmarineFlags\r
+            var stage1 = json.api_stage1;\r
+            if (stage1 == null || (stage1.api_f_count == 0 && stage1.api_e_count == 0))\r
+                return;\r
+            AirBattleResults.Add(new AirBattleResult\r
             {\r
-                Friend = (from status in _shipInfo.GetShipStatuses((int)json.api_dock_id - 1)\r
-                    select _shipMaster[status.ShipId].IsSubmarine).ToArray(),\r
-                Enemy = (from id in (int[])json.api_ship_ke where id != -1 select _shipMaster[id].IsSubmarine).ToArray()\r
-            };\r
+                PhaseName = phaseName,\r
+                AirControlLevel = json.api_stage1.api_disp_seiku() ? (int)json.api_stage1.api_disp_seiku : 0,\r
+                Stage1 = new AirBattleResult.StageResult\r
+                {\r
+                    FriendCount = (int)json.api_stage1.api_f_count,\r
+                    FriendLost = (int)json.api_stage1.api_f_lostcount,\r
+                    EnemyCount = (int)json.api_stage1.api_e_count,\r
+                    EnemyLost = (int)json.api_stage1.api_e_lostcount\r
+                },\r
+                Stage2 = json.api_stage2 == null\r
+                    ? new AirBattleResult.StageResult\r
+                    {\r
+                        FriendCount = 0,\r
+                        FriendLost = 0,\r
+                        EnemyCount = 0,\r
+                        EnemyLost = 0\r
+                    }\r
+                    : new AirBattleResult.StageResult\r
+                    {\r
+                        FriendCount = (int)json.api_stage2.api_f_count,\r
+                        FriendLost = (int)json.api_stage2.api_f_lostcount,\r
+                        EnemyCount = (int)json.api_stage2.api_e_count,\r
+                        EnemyLost = (int)json.api_stage2.api_e_lostcount\r
+                    }\r
+            });\r
+        }\r
+\r
+        private void CalcKoukuDamage(dynamic json)\r
+        {\r
+            if (!json.api_stage3() || json.api_stage3 == null)\r
+                return;\r
+            CalcSimpleDamage(json.api_stage3, _friend, _enemyHp);\r
+            if (json.api_stage3_combined())\r
+                CalcSimpleDamage(json.api_stage3_combined, _guard, _enemyGuardHp);\r
+        }\r
+\r
+        private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy)\r
+        {\r
+            if (json.api_fdam())\r
+                CalcSimpleDamage(json.api_fdam, friend);\r
+            if (json.api_edam())\r
+                CalcSimpleDamage(json.api_edam, enemy);\r
+        }\r
+\r
+        private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy, int[] enemyGuard)\r
+        {\r
+            CalcSimpleDamage(json.api_fdam, friend);\r
+            CalcSimpleDamage(json.api_edam, enemy, enemyGuard);\r
+        }\r
+\r
+        private void CalcSimpleDamage(dynamic json, Record[] friend, Record[] guard, int[] enemy, int[] enemyGuard)\r
+        {\r
+            CalcSimpleDamage(json.api_fdam, friend, guard);\r
+            CalcSimpleDamage(json.api_edam, enemy, enemyGuard);\r
+        }\r
+\r
+        private void CalcSimpleDamage(dynamic rawDamage, Record[] friend, Record[] guard)\r
+        {\r
+            var damage = (int[])rawDamage;\r
+            for (var i = 0; i < friend.Length; i++)\r
+                friend[i].ApplyDamage(damage[i + 1]);\r
+            for (var i = 0; i < guard.Length; i++)\r
+                guard[i].ApplyDamage(damage[i + 6 + 1]);\r
         }\r
 \r
-        private int AirFightDelay(dynamic json)\r
+        private void CalcSimpleDamage(dynamic rawDamage, Record[] friend)\r
         {\r
-            // 僚艦が偵察機だけだと航空戦に参加しないので、\r
-            // 艦載機の配備ではなく遭遇戦の状況を調べる。\r
-            var f = json.api_kouku.api_stage1.api_f_count > 0;\r
-            var e = json.api_kouku.api_stage1.api_e_count > 0;\r
-            var result = 0;\r
-            if (f || e) // 艦載機発艦\r
-                result += Delay.AirFight;\r
-            if (f && e) // 双方とも\r
-                result += Delay.AirFightBoth;\r
-            return result;\r
+            var damage = (int[])rawDamage;\r
+            for (var i = 0; i < friend.Length; i++)\r
+                friend[i].ApplyDamage(damage[i + 1]);\r
         }\r
 \r
-        private int SupportDelay(dynamic json)\r
+        private void CalcSimpleDamage(dynamic rawDamage, int[] enemy, int[] enemyGuard)\r
         {\r
-            return json.api_support_flag() && json.api_support_flag == 1 ? Delay.Support : 0;\r
+            var damage = (int[])rawDamage;\r
+            for (var i = 0; i < enemy.Length; i++)\r
+                enemy[i] -= damage[i + 1];\r
+            for (var i = 0; i < enemyGuard.Length; i++)\r
+                enemyGuard[i] -= damage[i + 6 + 1];\r
         }\r
 \r
-        private int CutinDelay(dynamic json)\r
+        private void CalcSimpleDamage(dynamic rawDamage, int[] result)\r
         {\r
-            var cutin = 0;\r
-            var maxHps = (int[])json.api_nowhps;\r
-            var nowHps = (int[])json.api_nowhps;\r
-            var planeFrom = (int[][])json.api_kouku.api_plane_from;\r
-            if ((planeFrom[0][0] != -1 || planeFrom[1][0] != -1) && // どちらかに艦載機あり\r
-                json.api_kouku.api_stage3 != null) // 敵艦載機が全滅しているとnull\r
+            var damage = (int[])rawDamage;\r
+            for (var i = 0; i < result.Length; i++)\r
+                result[i] -= damage[i + 1];\r
+        }\r
+\r
+        private void CalcHougekiDamage(dynamic hougeki, Record[] friend, int[] enemy)\r
+        {\r
+            var targets = ((dynamic[])hougeki.api_df_list).Skip(1).SelectMany(x => (int[])x);\r
+            var damages = ((dynamic[])hougeki.api_damage).Skip(1).SelectMany(x => (int[])x);\r
+            foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
             {\r
-                // 航空戦による中破大破の判定\r
-                var damages = (int[])json.api_kouku.api_stage3.api_fdam;\r
-                var newHps = nowHps.Zip(damages, (hp, dmg) => hp - dmg).ToArray();\r
-                if (IsCutinShown(nowHps, newHps, maxHps))\r
-                    cutin++;\r
-                nowHps = newHps;\r
+                if (hit.t == -1)\r
+                    continue;\r
+                if (hit.t <= 6)\r
+                    friend[hit.t - 1].ApplyDamage(hit.d);\r
+                else\r
+                    enemy[(hit.t - 1) % 6] -= hit.d;\r
             }\r
-            if ((int)json.api_opening_flag != 0)\r
+        }\r
+\r
+        private void CalcCombinedHougekiDamage(dynamic hougeki, Record[] friend, Record[] guard,\r
+            int[] enemy, int[] enemyGuard)\r
+        {\r
+            var targets = ((dynamic[])hougeki.api_df_list).Skip(1).Select(x => (int[])x);\r
+            var damages = ((dynamic[])hougeki.api_damage).Skip(1).Select(x => (int[])x);\r
+            var eflags = ((int[])hougeki.api_at_eflag).Skip(1);\r
+            foreach (var turn in\r
+                targets.Zip(damages, (t, d) => new {t, d}).\r
+                    Zip(eflags, (td, e) => new {e, td.t, td.d}))\r
             {\r
-                // 開幕雷撃による中破大破の判定\r
-                var damages = (int[])json.api_opening_atack.api_fdam;\r
-                var newHps = nowHps.Zip(damages, (hp, dmg) => hp - dmg).ToArray();\r
-                if (IsCutinShown(nowHps, newHps, maxHps))\r
-                    cutin++;\r
+                foreach (var hit in turn.t.Zip(turn.d, (t, d) => new {t, d}))\r
+                {\r
+                    if (turn.e == 1)\r
+                    {\r
+                        if (hit.t <= 6)\r
+                            friend[hit.t - 1].ApplyDamage(hit.d);\r
+                        else\r
+                            guard[(hit.t - 1) % 6].ApplyDamage(hit.d);\r
+                    }\r
+                    else\r
+                    {\r
+                        if (hit.t <= 6)\r
+                            enemy[hit.t - 1] -= hit.d;\r
+                        else\r
+                            enemyGuard[(hit.t - 1) % 6] -= hit.d;\r
+                    }\r
+                }\r
             }\r
-            return Delay.Cutin * cutin;\r
         }\r
 \r
-        private bool IsCutinShown(int[] nowHps, int[] newHps, int[] maxHps)\r
+        public void InspectBattleResult(dynamic json)\r
+        {\r
+            BattleState = BattleState.Result;\r
+            ShowResult();\r
+            CleanupResult();\r
+            SetEscapeShips(json);\r
+        }\r
+\r
+        public void InspectPracticeResult(dynamic json)\r
+        {\r
+            BattleState = BattleState.Result;\r
+            ShowResult(false);\r
+            CleanupResult();\r
+        }\r
+\r
+        private void ShowResult(bool warnDamagedShip = true)\r
         {\r
-            for (var i = 1; i < ShipInfo.MemberCount + 1; i++)\r
+            if (_friend == null)\r
+                return;\r
+            var ships = _guard.Length > 0\r
+                ? _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray()\r
+                : _shipInfo.GetShipStatuses(_fleet);\r
+            foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
+                entry.now.UpdateShipStatus(entry.ship);\r
+            if (warnDamagedShip)\r
+                _shipInfo.SetBadlyDamagedShips();\r
+            else\r
+                _shipInfo.ClearBadlyDamagedShips();\r
+            SetEnemyResultStatus();\r
+        }\r
+\r
+        private void SetEnemyResultStatus()\r
+        {\r
+            for (var i = 0; i < 6; i++)\r
             {\r
-                if (ShipStatus.CalcDamage(nowHps[i], maxHps[i]) <= ShipStatus.Damage.Small &&\r
-                    ShipStatus.CalcDamage(newHps[i], maxHps[i]) >= ShipStatus.Damage.Half)\r
-                    return true;\r
+                EnemyResultStatus[i].MaxHp = _enemyStartHp[i];\r
+                EnemyResultStatus[i].NowHp = _enemyHp[i];\r
+            }\r
+            for (var i = 6; i < EnemyResultStatus.Length; i++)\r
+            {\r
+                EnemyResultStatus[i].MaxHp = _enemyGuardStartHp[i - 6];\r
+                EnemyResultStatus[i].NowHp = _enemyGuardHp[i - 6];\r
             }\r
-            return false;\r
         }\r
 \r
-        private int CalcEnemyAirSuperiority(dynamic json)\r
+        public void SetEscapeShips(dynamic json)\r
         {\r
-            var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id => _shipMaster[id].MaxEq);\r
-            var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
-            return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
-                select (int)Math.Floor(_itemInfo.GetSpecByItemId(slot.id).TyKu * Math.Sqrt(slot.max))).Sum();\r
+            _escapingShips.Clear();\r
+            if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
+                return;\r
+            var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
+            _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
+            var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
+            _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
+        }\r
+\r
+        public void CauseCombinedBattleEscape()\r
+        {\r
+            _shipInfo.SetEscapedShips(_escapingShips);\r
+            _shipInfo.SetBadlyDamagedShips();\r
+        }\r
+\r
+        private class Record\r
+        {\r
+            private ShipStatus _status;\r
+            public int NowHp => _status.NowHp;\r
+            public bool Escaped => _status.Escaped;\r
+            public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
+            public int StartHp;\r
+\r
+            public static Record[] Setup(ShipStatus[] ships) =>\r
+                (from s in ships select new Record {_status = (ShipStatus)s.Clone(), StartHp = s.NowHp}).ToArray();\r
+\r
+            public void ApplyDamage(int damage)\r
+            {\r
+                if (_status.NowHp > damage)\r
+                {\r
+                    _status.NowHp -= damage;\r
+                    return;\r
+                }\r
+                _status.NowHp = 0;\r
+                foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))\r
+                {\r
+                    if (item.Spec.Id == 42)\r
+                    {\r
+                        _status.NowHp = (int)(_status.MaxHp * 0.2);\r
+                        ConsumeSlotItem(_status, 42);\r
+                        break;\r
+                    }\r
+                    if (item.Spec.Id == 43)\r
+                    {\r
+                        _status.NowHp = _status.MaxHp;\r
+                        ConsumeSlotItem(_status, 43);\r
+                        break;\r
+                    }\r
+                }\r
+            }\r
+\r
+            public void UpdateShipStatus(ShipStatus ship)\r
+            {\r
+                ship.NowHp = NowHp;\r
+                ship.Slot = _status.Slot;\r
+                ship.SlotEx = _status.SlotEx;\r
+            }\r
+        }\r
+\r
+        private BattleResultRank CalcLdAirBattleRank()\r
+        {\r
+            var combined = _friend.Concat(_guard).ToArray();\r
+            var friendNowShips = combined.Count(r => r.NowHp > 0);\r
+            var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);\r
+            var friendSunk = combined.Count(r => r.NowHp == 0);\r
+            var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
+\r
+            if (friendSunk == 0)\r
+            {\r
+                if (friendGauge == 0)\r
+                    return BattleResultRank.P;\r
+                if (friendGaugeRate < 10)\r
+                    return BattleResultRank.A;\r
+                if (friendGaugeRate < 20)\r
+                    return BattleResultRank.B;\r
+                if (friendGaugeRate < 50)\r
+                    return BattleResultRank.C;\r
+                return BattleResultRank.D;\r
+            }\r
+            if (friendSunk < friendNowShips)\r
+                return BattleResultRank.D;\r
+            return BattleResultRank.E;\r
+        }\r
+\r
+        private BattleResultRank CalcResultRank()\r
+        {\r
+            var friend = _friend.Concat(_guard).ToArray();\r
+            var enemyHp = _enemyHp.Concat(_enemyGuardHp).ToArray();\r
+            var enemyStartHp = _enemyStartHp.Concat(_enemyGuardStartHp).ToArray();\r
+\r
+            var friendCount = friend.Length;\r
+            var friendStartHpTotal = 0;\r
+            var friendNowHpTotal = 0;\r
+            var friendSunk = 0;\r
+            foreach (var ship in friend)\r
+            {\r
+                if (ship.Escaped)\r
+                    continue;\r
+                friendStartHpTotal += ship.StartHp;\r
+                friendNowHpTotal += ship.NowHp;\r
+                if (ship.NowHp == 0)\r
+                    friendSunk++;\r
+            }\r
+            var friendGaugeRate = (int)((double)(friendStartHpTotal - friendNowHpTotal) / friendStartHpTotal * 100);\r
+\r
+            var enemyCount = enemyHp.Length;\r
+            var enemyStartHpTotal = enemyStartHp.Sum();\r
+            var enemyNowHpTotal = enemyHp.Sum();\r
+            var enemySunk = enemyHp.Count(hp => hp == 0);\r
+            var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);\r
+\r
+            if (friendSunk == 0 && enemySunk == enemyCount)\r
+            {\r
+                if (friendNowHpTotal >= friendStartHpTotal)\r
+                    return BattleResultRank.P;\r
+                return BattleResultRank.S;\r
+            }\r
+            if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)\r
+                return BattleResultRank.A;\r
+            if (friendSunk < enemySunk && enemyHp[0] == 0)\r
+                return BattleResultRank.B;\r
+            if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
+                return BattleResultRank.D;\r
+            if (enemyGaugeRate > friendGaugeRate * 2.5)\r
+                return BattleResultRank.B;\r
+            if (enemyGaugeRate > friendGaugeRate * 0.9)\r
+                return BattleResultRank.C;\r
+            if (friendCount > 1 && friendCount - 1 == friendSunk)\r
+                return BattleResultRank.E;\r
+            return BattleResultRank.D;\r
         }\r
     }\r
 }
\ No newline at end of file