OSDN Git Service

連合艦隊時の僚艦夜戦突撃で第一旗艦に発動マークが付くのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / BattleInfo.cs
index 5fea3f2..6da3a26 100644 (file)
@@ -16,7 +16,6 @@ using System;
 using System.Collections.Generic;\r
 using System.Linq;\r
 using KancolleSniffer.Util;\r
-using KancolleSniffer.View;\r
 using static System.Math;\r
 \r
 namespace KancolleSniffer.Model\r
@@ -38,6 +37,7 @@ namespace KancolleSniffer.Model
         Day,\r
         Night,\r
         SpNight,\r
+        AirRaid,\r
         Result,\r
         Unknown\r
     }\r
@@ -50,32 +50,34 @@ namespace KancolleSniffer.Model
         public int Interception { get; set; }\r
     }\r
 \r
-    public class BattleInfo\r
+    public class BattleInfo : Sniffer.IPort\r
     {\r
         private readonly ShipInfo _shipInfo;\r
         private readonly ItemInfo _itemInfo;\r
+        private readonly AirBase _airBase;\r
         private Fleet _fleet;\r
         private Record[] _friend;\r
         private Record[] _guard;\r
         private Record[] _enemy;\r
         private Record[] _enemyGuard;\r
         private readonly List<int> _escapingShips = new List<int>();\r
-        private bool _lastCell;\r
+        private bool _safeCell;\r
 \r
         public BattleState BattleState { get; set; }\r
         public int[] Formation { get; private set; }\r
-        public int[] FighterPower { get; private set; }\r
+        public Range FighterPower { get; private set; }\r
         public EnemyFighterPower EnemyFighterPower { get; private set; }\r
         public int AirControlLevel { get; private set; }\r
         public BattleResultRank ResultRank { get; private set; }\r
         public RankPair DisplayedResultRank { get; } = new RankPair();\r
-        public BattleResult Result { get; set; }\r
+        public BattleResult Result { get; private set; }\r
         public bool EnemyIsCombined => _enemyGuard.Length > 0;\r
-        public List<AirBattleResult> AirBattleResults { get; } = new List<AirBattleResult>();\r
+        public AirBattleResult AirBattleResult { get; }\r
+        public int SupportType { get; private set; }\r
 \r
         public class RankPair\r
         {\r
-            public char Assumed { get; set; }\r
+            public char Assumed { get; set; } = 'X';\r
             public char Actual { get; set; }\r
             public bool IsError => Assumed != Actual;\r
         }\r
@@ -92,62 +94,135 @@ namespace KancolleSniffer.Model
             public Combined Enemy { get; set; }\r
         }\r
 \r
-        public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
+        public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo, AirBase airBase)\r
         {\r
             _shipInfo = shipInfo;\r
             _itemInfo = itemInfo;\r
+            _airBase = airBase;\r
+            AirBattleResult = new AirBattleResult(GetAirFireShipName, GetItemNames);\r
+        }\r
+\r
+        private string GetAirFireShipName(int idx)\r
+        {\r
+            return idx < _friend.Length ? _friend[idx].Name : _guard[idx - 6].Name;\r
+        }\r
+\r
+        private string[] GetItemNames(int[] ids)\r
+        {\r
+            return ids.Select(id => _itemInfo.GetSpecByItemId(id).Name).ToArray();\r
+        }\r
+\r
+        public void Port()\r
+        {\r
+            CleanupResult();\r
+            _safeCell = false;\r
+            BattleState = BattleState.None;\r
         }\r
 \r
         public void InspectBattle(string url, string request, dynamic json)\r
         {\r
-            if (json.api_formation())\r
-                Formation = ((dynamic[])json.api_formation).Select(f => f is string ? (int)int.Parse(f) : (int)f)\r
-                    .ToArray();\r
-            AirControlLevel = CheckAirControlLevel(json);\r
-            ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
-            SetupResult(request, json, url.Contains("practice"));\r
-            FighterPower = CalcFighterPower();\r
-            EnemyFighterPower = CalcEnemyFighterPower(json);\r
+            SetFormation(json);\r
+            SetSupportType(json);\r
+            ClearDamagedShipWarning();\r
+            ShowResult(); // 昼戦の結果を夜戦のときに表示する\r
+            SetupDamageRecord(request, json, url.Contains("practice"));\r
+            SetFighterPower();\r
+            SetEnemyFighterPower();\r
             BattleState = url.Contains("sp_midnight") ? BattleState.SpNight :\r
                 url.Contains("midnight") ? BattleState.Night : BattleState.Day;\r
+            if (BattleState != BattleState.Night)\r
+            {\r
+                AirBattleResult.Clear();\r
+                SetAirControlLevel(json);\r
+            }\r
             CalcDamage(json);\r
-            ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
+            ResultRank = url.Contains("/ld_") ? CalcLdResultRank() : CalcResultRank();\r
             SetResult();\r
         }\r
 \r
-        public static int DeckId(dynamic json)\r
+        private void SetFormation(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
+            if (json.api_formation())\r
+                Formation = (int[])json.api_formation;\r
         }\r
 \r
-        private void SetupResult(string request, dynamic json, bool practice)\r
+        private void SetAirControlLevel(dynamic json)\r
+        {\r
+            AirControlLevel = -1;\r
+            if (!json.api_kouku())\r
+                return;\r
+            var stage1 = json.api_kouku.api_stage1;\r
+            if (stage1 == null || stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
+                return;\r
+            AirControlLevel = (int)stage1.api_disp_seiku;\r
+        }\r
+\r
+        private void SetSupportType(dynamic json)\r
+        {\r
+            SupportType = json.api_support_flag() ? (int)json.api_support_flag :\r
+                json.api_n_support_flag() ? (int)json.api_n_support_flag : 0;\r
+        }\r
+\r
+        private void SetupDamageRecord(string request, dynamic json, bool practice)\r
         {\r
             if (_friend != null)\r
                 return;\r
             _shipInfo.SaveBattleStartStatus();\r
-            var fleets = _shipInfo.Fleets;\r
-            _fleet = fleets[DeckId(json)];\r
+            SetupFriendDamageRecord(request, json, practice);\r
+            SetupEnemyDamageRecord(json, practice);\r
+        }\r
+\r
+        private void SetupFriendDamageRecord(string request, dynamic json, bool practice)\r
+        {\r
+            _fleet = _shipInfo.Fleets[(int)json.api_deck_id - 1];\r
             FlagshipRecovery(request, _fleet.ActualShips[0]);\r
             _friend = Record.Setup(_fleet.ActualShips, practice);\r
             _guard = json.api_f_nowhps_combined()\r
-                ? Record.Setup(fleets[1].ActualShips, practice)\r
+                ? Record.Setup(_shipInfo.Fleets[1].ActualShips, practice)\r
                 : new Record[0];\r
+            SetEscapedFlag(json);\r
+        }\r
+\r
+        /// <summary>\r
+        /// EscapedはShipStatusにあるがBattleBriefTestの用のログにはShipStatusがないので、\r
+        /// ここで戦闘用のAPIを元に設定する。\r
+        /// </summary>\r
+        private void SetEscapedFlag(dynamic json)\r
+        {\r
+            if (json.api_escape_idx())\r
+            {\r
+                foreach (int idx in json.api_escape_idx)\r
+                    _friend[idx - 1].Escaped = true;\r
+            }\r
+            if (json.api_escape_idx_combined())\r
+            {\r
+                foreach (int idx in json.api_escape_idx_combined)\r
+                    _guard[idx - 1].Escaped = true;\r
+            }\r
+        }\r
+\r
+        private void SetupEnemyDamageRecord(dynamic json, bool practice)\r
+        {\r
             _enemy = Record.Setup((int[])json.api_e_nowhps,\r
-                ((int[])json.api_ship_ke).Select(_shipInfo.GetSpec).ToArray(),\r
-                ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray(),\r
-                practice);\r
+                EnemyShipSpecs(json.api_ship_ke),\r
+                EnemySlots(json.api_eSlot), practice);\r
             _enemyGuard = json.api_ship_ke_combined()\r
                 ? Record.Setup((int[])json.api_e_nowhps_combined,\r
-                    ((int[])json.api_ship_ke_combined).Select(_shipInfo.GetSpec).ToArray(),\r
-                    ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray())\r
-                    .ToArray(), practice)\r
+                    EnemyShipSpecs(json.api_ship_ke_combined),\r
+                    EnemySlots(json.api_eSlot_combined), practice)\r
                 : new Record[0];\r
         }\r
 \r
+        private ShipSpec[] EnemyShipSpecs(dynamic ships)\r
+        {\r
+            return ((int[])ships).Select(_shipInfo.GetSpec).ToArray();\r
+        }\r
+\r
+        private ItemSpec[][] EnemySlots(dynamic slots)\r
+        {\r
+            return ((int[][])slots).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray();\r
+        }\r
+\r
         private void SetResult()\r
         {\r
             Result = new BattleResult\r
@@ -202,65 +277,147 @@ namespace KancolleSniffer.Model
             }\r
         }\r
 \r
-        public void CleanupResult()\r
+        private void CleanupResult()\r
         {\r
             _friend = null;\r
-            _lastCell = false;\r
         }\r
 \r
-        private int CheckAirControlLevel(dynamic json)\r
+        private void SetFighterPower()\r
         {\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
+            var fleets = _shipInfo.Fleets;\r
+            FighterPower = _guard.Length > 0 && _enemyGuard.Length > 0\r
+                ? fleets[0].FighterPower + fleets[1].FighterPower\r
+                : _fleet.FighterPower;\r
         }\r
 \r
-        private int[] CalcFighterPower()\r
+        private void SetEnemyFighterPower()\r
         {\r
-            var fleets = _shipInfo.Fleets;\r
-            if (_guard.Length > 0 && _enemyGuard.Length > 0)\r
-                return fleets[0].FighterPower.Zip(fleets[1].FighterPower, (a, b) => a + b).ToArray();\r
-            return _fleet.FighterPower;\r
-        }\r
-\r
-        private EnemyFighterPower CalcEnemyFighterPower(dynamic json)\r
-        {\r
-            var result = new EnemyFighterPower();\r
-            var ships = (int[])json.api_ship_ke;\r
-            if (json.api_ship_ke_combined() && _guard.Length > 0)\r
-                ships = ships.Concat((int[])json.api_ship_ke_combined).ToArray();\r
-            var maxEq = ships.SelectMany(id =>\r
-            {\r
-                var r = _shipInfo.GetSpec(id).MaxEq;\r
-                if (r != null)\r
-                    return r;\r
-                result.HasUnknown = true;\r
-                return new int[5];\r
-            });\r
-            var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
-            if (json.api_eSlot_combined() && _guard.Length > 0)\r
-                equips = equips.Concat(((int[][])json.api_eSlot_combined).SelectMany(x => x));\r
-            foreach (var entry in from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
-                let spec = _itemInfo.GetSpecByItemId(slot.id)\r
-                let perSlot = (int)Floor(spec.AntiAir * Sqrt(slot.max))\r
-                select new {spec, perSlot})\r
-            {\r
-                if (entry.spec.CanAirCombat)\r
-                    result.AirCombat += entry.perSlot;\r
-                if (entry.spec.IsAircraft)\r
-                    result.Interception += entry.perSlot;\r
+            EnemyFighterPower = new EnemyFighterPower();\r
+            foreach (var record in _guard.Length == 0 ? _enemy : _enemy.Concat(_enemyGuard))\r
+            {\r
+                var ship = record.SnapShot;\r
+                if (ship.Spec.MaxEq == null)\r
+                {\r
+                    EnemyFighterPower.HasUnknown = true;\r
+                    continue;\r
+                }\r
+                foreach (var entry in ship.Slot.Zip(ship.Spec.MaxEq, (item, maxEq) => new {item.Spec, maxEq}))\r
+                {\r
+                    var perSlot = (int)Floor(entry.Spec.AntiAir * Sqrt(entry.maxEq));\r
+                    if (entry.Spec.CanAirCombat)\r
+                        EnemyFighterPower.AirCombat += perSlot;\r
+                    if (entry.Spec.IsAircraft)\r
+                        EnemyFighterPower.Interception += perSlot;\r
+                }\r
+            }\r
+        }\r
+\r
+        public void InspectMapStart(dynamic json)\r
+        {\r
+            InspectMapNext(json);\r
+        }\r
+\r
+        public void InspectMapNext(dynamic json)\r
+        {\r
+            SetSafeCell(json);\r
+            BattleState = BattleState.None;\r
+            if (!json.api_destruction_battle())\r
+                return;\r
+            InspectAirRaidBattle((int)json.api_maparea_id, json.api_destruction_battle);\r
+        }\r
+\r
+        private void SetSafeCell(dynamic json)\r
+        {\r
+            var map = (int)json.api_maparea_id * 1000 + (int)json.api_mapinfo_no * 100 + (int)json.api_no;\r
+            _safeCell =\r
+                (int)json.api_next == 0 || // last cell\r
+                map switch\r
+                {\r
+                    1613 => true, // 1-6-B\r
+                    1611 => true, // 1-6-D\r
+                    1616 => true, // 1-6-D\r
+                    2202 => true, // 2-2-B\r
+                    3102 => true, // 3-1-B\r
+                    3201 => true, // 3-2-A\r
+                    4206 => true, // 4-2-F\r
+                    5302 => true, // 5-3-B\r
+                    _ => false\r
+                };\r
+        }\r
+\r
+        private void InspectAirRaidBattle(int areaId, dynamic json)\r
+        {\r
+            SetFormation(json);\r
+            var attack = json.api_air_base_attack;\r
+            var stage1 = attack.api_stage1;\r
+            AirControlLevel = (int)stage1.api_disp_seiku;\r
+            var ships = (ShipStatus[])CreateShipsForAirBase(json);\r
+            _friend = Record.Setup(ships, false);\r
+            _guard = new Record[0];\r
+            FighterPower = _airBase.GetAirBase(areaId).CalcInterceptionFighterPower();\r
+            SetupEnemyDamageRecord(json, false);\r
+            SetEnemyFighterPower();\r
+            BattleState = BattleState.AirRaid;\r
+            AirBattleResult.Clear();\r
+            AirBattleResult.Add(json.api_air_base_attack, "空襲");\r
+            CalcKoukuDamage(json.api_air_base_attack);\r
+            SetAirRaidResultRank(json);\r
+            SetResult();\r
+            CleanupResult();\r
+        }\r
+\r
+        private ShipStatus[] CreateShipsForAirBase(dynamic json)\r
+        {\r
+            var nowHps = (int[])json.api_f_nowhps;\r
+            var maxHps = (int[])json.api_f_maxhps;\r
+            var maxEq = new[] {18, 18, 18, 18};\r
+            var ships = nowHps.Select((hp, n) => new ShipStatus\r
+            {\r
+                Id = 1,\r
+                Spec = new ShipSpec {Name = "基地航空隊" + (n + 1), GetMaxEq = () => maxEq},\r
+                NowHp = nowHps[n],\r
+                MaxHp = maxHps[n]\r
+            }).ToArray();\r
+            var planes = json.api_air_base_attack.api_map_squadron_plane;\r
+            if (planes == null)\r
+                return ships;\r
+            foreach (KeyValuePair<string, dynamic> entry in planes)\r
+            {\r
+                var num = int.Parse(entry.Key) - 1;\r
+                var slot = new List<ItemStatus>();\r
+                var onSlot = new List<int>();\r
+                foreach (var plane in entry.Value)\r
+                {\r
+                    slot.Add(new ItemStatus {Id = 1, Spec = _itemInfo.GetSpecByItemId((int)plane.api_mst_id)});\r
+                    onSlot.Add((int)plane.api_count);\r
+                }\r
+                ships[num].Slot = slot;\r
+                ships[num].OnSlot = onSlot.ToArray();\r
+            }\r
+            return ships;\r
+        }\r
+\r
+        private void SetAirRaidResultRank(dynamic json)\r
+        {\r
+            switch ((int)json.api_lost_kind)\r
+            {\r
+                case 1:\r
+                    ResultRank = BattleResultRank.A;\r
+                    break;\r
+                case 2:\r
+                    ResultRank = BattleResultRank.B;\r
+                    break;\r
+                case 3:\r
+                    ResultRank = BattleResultRank.C;\r
+                    break;\r
+                case 4:\r
+                    ResultRank = BattleResultRank.S;\r
+                    break;\r
             }\r
-            return result;\r
         }\r
 \r
         private void CalcDamage(dynamic json)\r
         {\r
-            AirBattleResults.Clear();\r
             foreach (KeyValuePair<string, dynamic> kv in json)\r
             {\r
                 if (kv.Value == null)\r
@@ -268,11 +425,11 @@ namespace KancolleSniffer.Model
                 switch (kv.Key)\r
                 {\r
                     case "api_air_base_injection":\r
-                        AddAirBattleResult(kv.Value, "AB噴式");\r
+                        AirBattleResult.Add(kv.Value, "AB噴式");\r
                         CalcKoukuDamage(kv.Value);\r
                         break;\r
                     case "api_injection_kouku":\r
-                        AddAirBattleResult(kv.Value, "噴式");\r
+                        AirBattleResult.Add(kv.Value, "噴式");\r
                         CalcKoukuDamage(kv.Value);\r
                         break;\r
                     case "api_air_base_attack":\r
@@ -288,11 +445,11 @@ namespace KancolleSniffer.Model
                         CalcDamageByTurn(kv.Value);\r
                         break;\r
                     case "api_kouku":\r
-                        AddAirBattleResult(kv.Value, "航空戦");\r
+                        AirBattleResult.Add(kv.Value, "航空戦");\r
                         CalcKoukuDamage(kv.Value);\r
                         break;\r
                     case "api_kouku2":\r
-                        AddAirBattleResult(kv.Value, "航空戦2");\r
+                        AirBattleResult.Add(kv.Value, "航空戦2");\r
                         CalcKoukuDamage(kv.Value);\r
                         break;\r
                     case "api_support_info":\r
@@ -343,7 +500,7 @@ namespace KancolleSniffer.Model
             var i = 1;\r
             foreach (var entry in json)\r
             {\r
-                AddAirBattleResult(entry, "基地" + i++);\r
+                AirBattleResult.Add(entry, "基地" + i++);\r
                 CalcKoukuDamage(entry);\r
             }\r
         }\r
@@ -353,52 +510,6 @@ namespace KancolleSniffer.Model
             CalcDamageByTurn(json.api_hougeki, true);\r
         }\r
 \r
-        private void AddAirBattleResult(dynamic json, string phaseName)\r
-        {\r
-            var stage1 = json.api_stage1;\r
-            if (stage1 == null || (stage1.api_f_count == 0 && stage1.api_e_count == 0))\r
-                return;\r
-            var result = new AirBattleResult\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
-            if (json.api_stage2 != null && json.api_stage2.api_air_fire())\r
-            {\r
-                var airFire = json.api_stage2.api_air_fire;\r
-                var idx = (int)airFire.api_idx;\r
-                result.AirFire = new AirBattleResult.AirFireResult\r
-                {\r
-                    ShipName = idx < _friend.Length ? _friend[idx].Name : _guard[idx - 6].Name,\r
-                    Kind = (int)airFire.api_kind,\r
-                    Items = ((int[])airFire.api_use_items).Select(id => _itemInfo.GetSpecByItemId(id).Name).ToArray()\r
-                };\r
-            }\r
-            AirBattleResults.Add(result);\r
-        }\r
-\r
         private void CalcKoukuDamage(dynamic json)\r
         {\r
             if (json.api_stage3() && json.api_stage3 != null)\r
@@ -437,7 +548,10 @@ namespace KancolleSniffer.Model
             if (guard == null)\r
                 return;\r
             for (var i = 0; i < guard.Length; i++)\r
+            {\r
                 guard[i].ApplyDamage(damage[i + 6]);\r
+                guard[i].CheckDamageControl();\r
+            }\r
         }\r
 \r
         private void CalcDamageByTurn(dynamic json, bool ignoreFriendDamage = false)\r
@@ -448,42 +562,71 @@ namespace KancolleSniffer.Model
                 return;\r
 \r
             var eFlags = (int[])json.api_at_eflag;\r
-            var sources = (int[])json.api_at_list;\r
-            var types = json.api_at_type() ? (int[])json.api_at_type : null;\r
+            var night = json.api_sp_list();\r
+            var types = night ? (int[])json.api_sp_list : (int[])json.api_at_type;\r
             var targets = (int[][])json.api_df_list;\r
             var damages = (int[][])json.api_damage;\r
-            var records = new[] {new Record[12], new Record[12]};\r
-            Array.Copy(_friend, records[1], _friend.Length);\r
-            Array.Copy(_guard, 0, records[1], 6, _guard.Length);\r
-            Array.Copy(_enemy, records[0], _enemy.Length);\r
-            Array.Copy(_enemyGuard, 0, records[0], 6, _enemyGuard.Length);\r
+            var records = new BothRecord(_friend, _guard, _enemy, _enemyGuard);\r
             for (var turn = 0; turn < eFlags.Length; turn++)\r
             {\r
                 if (ignoreFriendDamage && eFlags[turn] == 1)\r
                     continue;\r
-                if (types != null && types[turn] == 100) // Nelson Touch\r
-                    records[eFlags[turn] ^ 1][sources[turn]].TriggerSpecialAttack();\r
+                if (IsSpecialAttack(types[turn]))\r
+                {\r
+                    var pos = night && _guard.Length > 0 ? 6 : 0; // 連合第二の僚艦夜戦突撃は6\r
+                    records.TriggerSpecialAttack(1, pos);\r
+                }\r
                 for (var shot = 0; shot < targets[turn].Length; shot++)\r
                 {\r
                     var target = targets[turn][shot];\r
                     var damage = damages[turn][shot];\r
                     if (target == -1 || damage == -1)\r
                         continue;\r
-                    records[eFlags[turn]][target].ApplyDamage(damage);\r
+                    records.ApplyDamage(eFlags[turn], target, damage);\r
                 }\r
-                foreach (var ship in records[1])\r
-                    ship?.CheckDamageControl();\r
+                records.CheckDamageControl();\r
             }\r
         }\r
 \r
-        public void InspectMapStart(dynamic json)\r
+        private bool IsSpecialAttack(int type)\r
         {\r
-            InspectMapNext(json);\r
+            // 100: Nelson Touch\r
+            // 101: 長門一斉射\r
+            // 102: 陸奥一斉射\r
+            // 104: 金剛僚艦夜戦突撃\r
+            // 200: 瑞雲一体攻撃\r
+            // 201: 海陸立体攻撃\r
+            return type >= 100 && type < 200;\r
         }\r
 \r
-        public void InspectMapNext(dynamic json)\r
+        private class BothRecord\r
         {\r
-            _lastCell = (int)json.api_next == 0;\r
+            private readonly Record[][] _records;\r
+\r
+            public BothRecord(Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
+            {\r
+                _records = new[] {new Record[12], new Record[12]};\r
+                Array.Copy(friend, _records[1], friend.Length);\r
+                Array.Copy(guard, 0, _records[1], 6, guard.Length);\r
+                Array.Copy(enemy, _records[0], enemy.Length);\r
+                Array.Copy(enemyGuard, 0, _records[0], 6, enemyGuard.Length);\r
+            }\r
+\r
+            public void TriggerSpecialAttack(int side, int index)\r
+            {\r
+                _records[side][index].TriggerSpecialAttack();\r
+            }\r
+\r
+            public void ApplyDamage(int side, int index, int damage)\r
+            {\r
+                _records[side][index].ApplyDamage(damage);\r
+            }\r
+\r
+            public void CheckDamageControl()\r
+            {\r
+                foreach (var ship in _records[1])\r
+                    ship?.CheckDamageControl();\r
+            }\r
         }\r
 \r
         public void InspectBattleResult(dynamic json)\r
@@ -491,7 +634,8 @@ namespace KancolleSniffer.Model
             BattleState = BattleState.Result;\r
             if (_friend == null)\r
                 return;\r
-            ShowResult(!_lastCell);\r
+            ShowResult();\r
+            SetDamagedShipWarning();\r
             _shipInfo.SaveBattleResult();\r
             _shipInfo.DropShipId = json.api_get_ship() ? (int)json.api_get_ship.api_ship_id : -1;\r
             VerifyResultRank(json);\r
@@ -504,12 +648,12 @@ namespace KancolleSniffer.Model
             BattleState = BattleState.Result;\r
             if (_friend == null)\r
                 return;\r
-            ShowResult(false);\r
+            ShowResult();\r
             VerifyResultRank(json);\r
             CleanupResult();\r
         }\r
 \r
-        private void ShowResult(bool warnDamagedShip = true)\r
+        private void ShowResult()\r
         {\r
             if (_friend == null)\r
                 return;\r
@@ -519,10 +663,18 @@ namespace KancolleSniffer.Model
                 : _fleet.ActualShips;\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
+        }\r
+\r
+        private void SetDamagedShipWarning()\r
+        {\r
+            if (_safeCell)\r
+                return;\r
+            _shipInfo.SetBadlyDamagedShips();\r
+        }\r
+\r
+        private void ClearDamagedShipWarning()\r
+        {\r
+            _shipInfo.ClearBadlyDamagedShips();\r
         }\r
 \r
         private void VerifyResultRank(dynamic json)\r
@@ -537,7 +689,7 @@ namespace KancolleSniffer.Model
             DisplayedResultRank.Actual = actual;\r
         }\r
 \r
-        public void SetEscapeShips(dynamic json)\r
+        private void SetEscapeShips(dynamic json)\r
         {\r
             _escapingShips.Clear();\r
             if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
@@ -567,14 +719,15 @@ namespace KancolleSniffer.Model
             private bool _practice;\r
             public ShipStatus SnapShot => (ShipStatus)_status.Clone();\r
             public int NowHp => _status.NowHp;\r
-            public bool Escaped => _status.Escaped;\r
+            public bool Escaped { get; set; }\r
             public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
             public string Name => _status.Name;\r
             public int StartHp { get; private set; }\r
 \r
             public static Record[] Setup(IEnumerable<ShipStatus> ships, bool practice) =>\r
-            (from s in ships\r
-                select new Record {_status = (ShipStatus)s.Clone(), _practice = practice, StartHp = s.NowHp}).ToArray();\r
+                (from s in ships\r
+                    select new Record {_status = (ShipStatus)s.Clone(), _practice = practice, StartHp = s.NowHp})\r
+                .ToArray();\r
 \r
             public static Record[] Setup(int[] nowHps, ShipSpec[] specs, ItemSpec[][] slots, bool practice)\r
             {\r
@@ -631,78 +784,69 @@ namespace KancolleSniffer.Model
                 ship.NowHp = NowHp;\r
                 ship.Slot = _status.Slot;\r
                 ship.SlotEx = _status.SlotEx;\r
-                ship.SpecialAttack = _status.SpecialAttack == ShipStatus.Attack.Fire\r
-                    ? ShipStatus.Attack.Fired\r
-                    : ShipStatus.Attack.None;\r
+                if (_status.SpecialAttack == ShipStatus.Attack.Fire)\r
+                    ship.SpecialAttack = ShipStatus.Attack.Fired;\r
             }\r
         }\r
 \r
-        private BattleResultRank CalcLdAirBattleRank()\r
+        private BattleResultRank CalcLdResultRank()\r
         {\r
-            var combined = _friend.Concat(_guard).Where(r => !r.Escaped).ToArray();\r
-            var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);\r
-            var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
+            var friend = new ResultRankParams(_friend.Concat(_guard).ToArray());\r
 \r
-            if (friendGauge <= 0)\r
+            if (friend.Gauge <= 0)\r
                 return BattleResultRank.P;\r
-            if (friendGaugeRate < 10)\r
+            if (friend.GaugeRate < 10)\r
                 return BattleResultRank.A;\r
-            if (friendGaugeRate < 20)\r
+            if (friend.GaugeRate < 20)\r
                 return BattleResultRank.B;\r
-            if (friendGaugeRate < 50)\r
+            if (friend.GaugeRate < 50)\r
                 return BattleResultRank.C;\r
-            if (friendGaugeRate < 80)\r
+            if (friend.GaugeRate < 80)\r
                 return BattleResultRank.D;\r
             return BattleResultRank.E;\r
         }\r
 \r
         private BattleResultRank CalcResultRank()\r
         {\r
-            var friend = _friend.Concat(_guard).ToArray();\r
-            var enemy = _enemy.Concat(_enemyGuard).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
+            var friend = new ResultRankParams(_friend.Concat(_guard).ToArray());\r
+            var enemy = new ResultRankParams(_enemy.Concat(_enemyGuard).ToArray());\r
+            if (friend.Sunk == 0 && enemy.Sunk == enemy.Count)\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 = enemy.Length;\r
-            var enemyStartHpTotal = enemy.Sum(r => r.StartHp);\r
-            var enemyNowHpTotal = enemy.Sum(r => r.NowHp);\r
-            var enemySunk = enemy.Count(r => r.NowHp == 0);\r
-            var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);\r
-\r
-            if (friendSunk == 0 && enemySunk == enemyCount)\r
-            {\r
-                if (friendNowHpTotal >= friendStartHpTotal)\r
+                if (friend.Gauge <= 0)\r
                     return BattleResultRank.P;\r
                 return BattleResultRank.S;\r
             }\r
-            if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)\r
+            if (friend.Sunk == 0 && enemy.Sunk >= (int)(enemy.Count * 0.7) && enemy.Count > 1)\r
                 return BattleResultRank.A;\r
-            if (friendSunk < enemySunk && enemy[0].NowHp == 0)\r
+            if (friend.Sunk < enemy.Sunk && _enemy[0].NowHp == 0)\r
                 return BattleResultRank.B;\r
-            if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
+            if (friend.Count == 1 && _friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
                 return BattleResultRank.D;\r
-            if (enemyGaugeRate > friendGaugeRate * 2.5)\r
+            if (enemy.GaugeRate > friend.GaugeRate * 2.5)\r
                 return BattleResultRank.B;\r
-            if (enemyGaugeRate > friendGaugeRate * 0.9)\r
+            if (enemy.GaugeRate > friend.GaugeRate * 0.9)\r
                 return BattleResultRank.C;\r
-            if (friendCount > 1 && friendCount - 1 == friendSunk)\r
+            if (friend.Count > 1 && friend.Count - 1 == friend.Sunk)\r
                 return BattleResultRank.E;\r
             return BattleResultRank.D;\r
         }\r
 \r
+        private class ResultRankParams\r
+        {\r
+            public readonly int Count;\r
+            public readonly int Sunk;\r
+            public readonly int Gauge;\r
+            public readonly int GaugeRate;\r
+\r
+            public ResultRankParams(Record[] records)\r
+            {\r
+                Count = records.Length;\r
+                Sunk = records.Count(r => r.NowHp == 0);\r
+                Gauge = records.Sum(r => r.StartHp - r.NowHp);\r
+                GaugeRate = (int)((double)Gauge / records.Sum(r => r.Escaped ? 0 : r.StartHp) * 100);\r
+            }\r
+        }\r
+\r
         /// <summary>\r
         /// テスト専用\r
         /// </summary>\r