OSDN Git Service

敵連合艦隊との戦闘に対応する
authorKazuhiro Fujieda <fujieda@users.osdn.me>
Sun, 16 Oct 2016 12:25:42 +0000 (21:25 +0900)
committerKazuhiro Fujieda <fujieda@users.osdn.me>
Sun, 16 Oct 2016 12:25:42 +0000 (21:25 +0900)
KancolleSniffer.Test/SnifferTest.cs
KancolleSniffer.Test/logs
KancolleSniffer/BattleInfo.cs
KancolleSniffer/Sniffer.cs

index 30d2313..6658355 100644 (file)
@@ -497,6 +497,25 @@ namespace KancolleSniffer.Test
         }\r
 \r
         /// <summary>\r
+        /// 敵の連合艦隊に対応する\r
+        /// </summary>\r
+        [TestMethod]\r
+        public void EnemyCombinedBattle()\r
+        {\r
+            var sniffer1 = new Sniffer();\r
+            SniffLogFile(sniffer1, "ec_battle_001");\r
+            PAssert.That(() => sniffer1.Battle.ResultRank == BattleResultRank.S, "昼戦のみ");\r
+\r
+            var sniffer2 = new Sniffer();\r
+            SniffLogFile(sniffer2, "ec_battle_002");\r
+            PAssert.That(() => sniffer2.Battle.ResultRank == BattleResultRank.S, "夜戦込み");\r
+\r
+            var sniffer3 = new Sniffer();\r
+            SniffLogFile(sniffer3, "ec_battle_003");\r
+            PAssert.That(() => sniffer3.Battle.ResultRank == BattleResultRank.A, "護衛を撃ちもらす");\r
+        }\r
+\r
+        /// <summary>\r
         /// 2-5をクリアしたときの特別戦果を反映する\r
         /// </summary>\r
         [TestMethod]\r
index 146f8b0..03cfa5f 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 146f8b02d323d5f8ef29b701e26a29f72f87998e
+Subproject commit 03cfa5f6b12cc612f2ad94af0f3d9f4a5f33f0a4
index 8df6119..2926568 100644 (file)
@@ -37,7 +37,9 @@ namespace KancolleSniffer
         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
@@ -63,9 +65,15 @@ namespace KancolleSniffer
             ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
             SetupResult(json);\r
             if (IsNightBattle(json))\r
-                CalcHougekiDamage(json.api_hougeki, _friend, _enemyHp);\r
+            {\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
-                CalcDamage(json);\r
+            {\r
+                CalcDamage(json, url.EndsWith("battle_water"));\r
+            }\r
             ClearOverKill(_enemyHp);\r
             ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
         }\r
@@ -78,6 +86,8 @@ namespace KancolleSniffer
             _flagshipRecoveryType = int.Parse(type);\r
         }\r
 \r
+        private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
+\r
         private int DeckId(dynamic json)\r
         {\r
             if (json.api_dock_id()) // 昼戦はtypoしている\r
@@ -109,26 +119,31 @@ namespace KancolleSniffer
         {\r
             if (_friend != null)\r
                 return;\r
-            var combined = json.api_nowhps_combined();\r
             var nowhps = (int[])json.api_nowhps;\r
-            _fleet = combined ? 0 : DeckId(json);\r
+            _fleet = DeckId(json);\r
             var fstats = _shipInfo.GetShipStatuses(_fleet);\r
             FlagshipRecovery(fstats[0]);\r
             _friend = Record.Setup(fstats);\r
             _enemyHp = nowhps.Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
             _enemyStartHp = (int[])_enemyHp.Clone();\r
             EnemyResultStatus =\r
-                (from id in ((int[])json.api_ship_ke).Skip(1)\r
-                    where id != -1\r
-                    select new ShipStatus {Id = id, Spec = _shipInfo.GetSpec(id)}).ToArray();\r
-            if (combined)\r
-            {\r
-                var gstats = _shipInfo.GetShipStatuses(1);\r
-                _guard = Record.Setup(gstats);\r
-            }\r
-            else\r
+            (from id in (int[])json.api_ship_ke\r
+                where id != -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
-                _guard = new Record[0];\r
+                _enemyGuardHp =\r
+                    ((int[])json.api_nowhps_combined).\r
+                        Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
+                _enemyGuardStartHp = (int[])_enemyGuardHp.Clone();\r
             }\r
         }\r
 \r
@@ -199,26 +214,27 @@ namespace KancolleSniffer
             }).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
+                       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
-            var combined = json.api_nowhps_combined();\r
+            var fc = _guard.Length > 0;\r
+            var ec = _enemyGuardHp.Length > 0;\r
             if (json.api_air_base_attack())\r
                 CalcAirBaseAttackDamage(json.api_air_base_attack);\r
             if (json.api_kouku.api_stage3 != null)\r
                 CalcSimpleDamage(json.api_kouku.api_stage3, _friend, _enemyHp);\r
             if (json.api_kouku.api_stage3_combined() && json.api_kouku.api_stage3_combined != null)\r
-                CalcSimpleDamage(json.api_kouku.api_stage3_combined.api_fdam, _guard);\r
+                CalcSimpleDamage(json.api_kouku.api_stage3_combined, _guard, _enemyGuardHp);\r
             if (json.api_kouku2()) // 航空戦2回目\r
             {\r
                 if (json.api_kouku2.api_stage3 != null)\r
                     CalcSimpleDamage(json.api_kouku2.api_stage3, _friend, _enemyHp);\r
                 if (json.api_kouku2.api_stage3_combined() && json.api_kouku2.api_stage3_combined != null)\r
-                    CalcSimpleDamage(json.api_kouku2.api_stage3_combined.api_fdam, _guard);\r
+                    CalcSimpleDamage(json.api_kouku2.api_stage3_combined, _guard, _enemyGuardHp);\r
             }\r
             if (!json.api_opening_atack()) // 航空戦のみ\r
                 return;\r
@@ -226,32 +242,43 @@ namespace KancolleSniffer
                 CalcSupportDamage(json.api_support_info);\r
             if (json.api_opening_taisen() && json.api_opening_taisen != null)\r
             {\r
-                var friend = combined ? _guard : _friend; // 先制対潜攻撃の対象は護衛(たぶん)\r
+                var friend = fc ? _guard : _friend; // 先制対潜攻撃の対象は護衛(たぶん)\r
                 CalcHougekiDamage(json.api_opening_taisen, friend, _enemyHp);\r
             }\r
             if (json.api_opening_atack != null)\r
             {\r
-                var friend = combined ? _guard : _friend; // 雷撃の対象は護衛\r
-                CalcSimpleDamage(json.api_opening_atack, friend, _enemyHp);\r
+                var friend = fc ? _guard : _friend; // 雷撃の対象は護衛\r
+                CalcSimpleDamage(json.api_opening_atack, friend, _enemyHp, _enemyGuardHp);\r
             }\r
             if (json.api_hougeki1() && json.api_hougeki1 != null)\r
             {\r
-                var friend = combined && !surfaceFleet ? _guard : _friend; // 空母機動部隊は一巡目が護衛\r
-                CalcHougekiDamage(json.api_hougeki1, friend, _enemyHp);\r
+                CalcHougekiDamage(json.api_hougeki1,\r
+                    fc && !surfaceFleet ? _guard : _friend, // 空母機動部隊は一巡目が護衛\r
+                    ec ? _enemyGuardHp : _enemyHp); // 敵連合艦隊は一巡目が護衛\r
             }\r
             if (json.api_hougeki2() && json.api_hougeki2 != null)\r
             {\r
-                CalcHougekiDamage(json.api_hougeki2, _friend, _enemyHp);\r
+                if (json.api_hougeki2.api_at_eflag())\r
+                    CalcCombinedHougekiDamage(json.api_hougeki2, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                else\r
+                    CalcHougekiDamage(json.api_hougeki2, _friend, _enemyHp);\r
             }\r
             if (json.api_hougeki3() && json.api_hougeki3 != null)\r
             {\r
-                var friend = combined && surfaceFleet ? _guard : _friend; // 水上打撃部隊は三順目が護衛\r
-                CalcHougekiDamage(json.api_hougeki3, friend, _enemyHp);\r
+                if (json.api_hougeki3.api_at_eflag())\r
+                {\r
+                    CalcCombinedHougekiDamage(json.api_hougeki3, _friend, _guard, _enemyHp, _enemyGuardHp);\r
+                }\r
+                else\r
+                {\r
+                    var friend = fc && surfaceFleet ? _guard : _friend; // 水上打撃部隊は三順目が護衛\r
+                    CalcHougekiDamage(json.api_hougeki3, friend, _enemyHp);\r
+                }\r
             }\r
             if (json.api_raigeki() && json.api_raigeki != null)\r
             {\r
-                var friend = combined ? _guard : _friend;\r
-                CalcSimpleDamage(json.api_raigeki, friend, _enemyHp);\r
+                var friend = fc ? _guard : _friend;\r
+                CalcSimpleDamage(json.api_raigeki, friend, _enemyHp, _enemyGuardHp);\r
             }\r
         }\r
 \r
@@ -274,15 +301,27 @@ namespace KancolleSniffer
                 if (!entry.api_stage3() || entry.api_stage3 == null)\r
                     continue;\r
                 CalcSimpleDamage(entry.api_stage3.api_edam, _enemyHp);\r
+                if (entry.api_stage3_combined())\r
+                    CalcSimpleDamage(entry.api_stage3_combined.api_edam, _enemyGuardHp);\r
             }\r
         }\r
 \r
-        private bool IsNightBattle(dynamic json) => json.api_hougeki();\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);\r
+            var damage = (int[])json.api_edam;\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 void CalcSimpleDamage(dynamic rawDamage, Record[] result)\r
@@ -302,15 +341,45 @@ namespace KancolleSniffer
         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 => (double[])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
                 if (hit.t == -1)\r
                     continue;\r
                 if (hit.t <= 6)\r
-                    friend[hit.t - 1].ApplyDamage((int)hit.d);\r
+                    friend[hit.t - 1].ApplyDamage(hit.d);\r
                 else\r
-                    enemy[(hit.t - 1) % 6] -= (int)hit.d;\r
+                    enemy[(hit.t - 1) % 6] -= hit.d;\r
+            }\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
+                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].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
         }\r
 \r
@@ -325,6 +394,7 @@ namespace KancolleSniffer
         {\r
             ShowResult();\r
             CleanupResult();\r
+            SetEscapeShips(json);\r
         }\r
 \r
         public void InspectPracticeResult(dynamic json)\r
@@ -337,9 +407,11 @@ namespace KancolleSniffer
         {\r
             if (_friend == null)\r
                 return;\r
-            var ships = _shipInfo.GetShipStatuses(_fleet);\r
-            foreach (var e in ships.Zip(_friend, (ship, now) => new {ship, now}))\r
-                e.now.UpdateShipStatus(e.ship);\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
@@ -356,29 +428,10 @@ namespace KancolleSniffer
             }\r
         }\r
 \r
-        public void InspectCombinedBattle(dynamic json, string url)\r
-        {\r
-            InBattle = true;\r
-            Formation = FormationName(json);\r
-            EnemyFighterPower = CalcEnemyFighterPower(json);\r
-            AirControlLevel = CheckAirControlLevel(json);\r
-            _fleet = 10;\r
-            ShowResultCombined(false);\r
-            SetupResult(json);\r
-            if (IsNightBattle(json))\r
-                CalcHougekiDamage(json.api_hougeki, _guard, _enemyHp);\r
-            else\r
-                CalcDamage(json, url.EndsWith("battle_water"));\r
-            ClearOverKill(_enemyHp);\r
-            ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
-        }\r
-\r
-        public void InspectCombinedBattleResult(dynamic json)\r
+        public void SetEscapeShips(dynamic json)\r
         {\r
             _escapingShips.Clear();\r
-            ShowResultCombined();\r
-            CleanupResult();\r
-            if ((int)json.api_escape_flag == 0)\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
@@ -386,20 +439,6 @@ namespace KancolleSniffer
             _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
         }\r
 \r
-        private void ShowResultCombined(bool warnDamagedShip = true)\r
-        {\r
-            if (_friend == null)\r
-                return;\r
-            var ships = _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray();\r
-            foreach (var e in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
-                e.now.UpdateShipStatus(e.ship);\r
-            if (warnDamagedShip)\r
-                _shipInfo.SetBadlyDamagedShips();\r
-            else\r
-                _shipInfo.ClearBadlyDamagedShips();\r
-            SetEnemyResultStatus();\r
-        }\r
-\r
         public void CauseCombinedBattleEscape()\r
         {\r
             _shipInfo.SetEscapedShips(_escapingShips);\r
@@ -414,7 +453,7 @@ namespace KancolleSniffer
             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
+                (from s in ships select new Record {_status = (ShipStatus)s.Clone(), StartHp = s.NowHp}).ToArray();\r
 \r
             public void ApplyDamage(int damage)\r
             {\r
@@ -501,21 +540,23 @@ namespace KancolleSniffer
         //\r
         private BattleResultRank CalcResultRank()\r
         {\r
-            var combined = _friend.Concat(_guard).ToArray();\r
+            var friend = _friend.Concat(_guard).ToArray();\r
+            var enemyHp = _enemyHp.Concat(_enemyGuardHp).ToArray();\r
+            var enemyStartHp = _enemyStartHp.Concat(_enemyGuardStartHp).ToArray();\r
             // 戦闘後に残っている艦数\r
-            var friendNowShips = combined.Count(r => r.NowHp > 0);\r
-            var enemyNowShips = _enemyHp.Count(hp => hp > 0);\r
+            var friendNowShips = friend.Count(r => r.NowHp > 0);\r
+            var enemyNowShips = enemyHp.Count(hp => hp > 0);\r
             // 総ダメージ\r
-            var friendGauge = Max(combined.Sum(r => r.StartHp - r.NowHp), 0); // ダメコン・女神発動で負になりうる\r
-            var enemyGauge = _enemyStartHp.Sum() - _enemyHp.Sum();\r
+            var friendGauge = Max(friend.Sum(r => r.StartHp - r.NowHp), 0); // ダメコン・女神発動で負になりうる\r
+            var enemyGauge = enemyStartHp.Sum() - enemyHp.Sum();\r
             // 轟沈・撃沈数\r
-            var friendSunk = combined.Count(r => r.NowHp == 0);\r
-            var enemySunk = _enemyHp.Count(hp => hp == 0);\r
+            var friendSunk = friend.Count(r => r.NowHp == 0);\r
+            var enemySunk = enemyHp.Count(hp => hp == 0);\r
 \r
-            var friendGaugeRate = Floor((double)friendGauge / combined.Where(r => !r.Escaped).Sum(r => r.StartHp) * 100);\r
-            var enemyGaugeRate = Floor((double)enemyGauge / _enemyStartHp.Sum() * 100);\r
-            var equalOrMore = enemyGaugeRate > (0.9 * friendGaugeRate);\r
-            var superior = enemyGaugeRate > 0 && enemyGaugeRate > (2.5 * friendGaugeRate);\r
+            var friendGaugeRate = Floor((double)friendGauge / friend.Where(r => !r.Escaped).Sum(r => r.StartHp) * 100);\r
+            var enemyGaugeRate = Floor((double)enemyGauge / enemyStartHp.Sum() * 100);\r
+            var equalOrMore = enemyGaugeRate > 0.9 * friendGaugeRate;\r
+            var superior = enemyGaugeRate > 0 && enemyGaugeRate > 2.5 * friendGaugeRate;\r
 \r
             if (friendSunk == 0)\r
             {\r
@@ -525,7 +566,7 @@ namespace KancolleSniffer
                         return BattleResultRank.P;\r
                     return BattleResultRank.S;\r
                 }\r
-                if (_enemyHp.Length == 6)\r
+                if (enemyHp.Length == 6)\r
                 {\r
                     if (enemySunk >= 4)\r
                         return BattleResultRank.A;\r
index 4107a48..8dec077 100644 (file)
@@ -301,7 +301,7 @@ namespace KancolleSniffer
 \r
         private Update ApiBattle(string url, string request, dynamic data)\r
         {\r
-            if (IsNormalBattleAPI(url))\r
+            if (IsNormalBattleAPI(url) || IsCombinedBattleAPI(url))\r
             {\r
                 _battleInfo.InspectBattle(data, url);\r
                 _logger.InspectBattle(data);\r
@@ -318,7 +318,7 @@ namespace KancolleSniffer
                 _battleInfo.InspectBattle(data, url);\r
                 return Update.Ship | Update.Battle | Update.Timer;\r
             }\r
-            if (url.EndsWith("api_req_sortie/battleresult"))\r
+            if (url.EndsWith("api_req_sortie/battleresult") || url.EndsWith("api_req_combined_battle/battleresult"))\r
             {\r
                 _battleInfo.InspectBattleResult(data);\r
                 _exMapInfo.InspectBattleResult(data);\r
@@ -330,18 +330,6 @@ namespace KancolleSniffer
                 _battleInfo.InspectPracticeResult(data);\r
                 return Update.Ship;\r
             }\r
-            if (IsCombinedBattleAPI(url))\r
-            {\r
-                _battleInfo.InspectCombinedBattle(data, url);\r
-                _logger.InspectBattle(data);\r
-                return Update.Ship | Update.Battle;\r
-            }\r
-            if (url.EndsWith("api_req_combined_battle/battleresult"))\r
-            {\r
-                _battleInfo.InspectCombinedBattleResult(data);\r
-                _logger.InspectBattleResult(data);\r
-                return Update.Ship;\r
-            }\r
             if (url.EndsWith("api_req_combined_battle/goback_port"))\r
             {\r
                 _battleInfo.CauseCombinedBattleEscape();\r
@@ -366,7 +354,9 @@ namespace KancolleSniffer
                    url.EndsWith("api_req_combined_battle/ld_airbattle") ||\r
                    url.EndsWith("api_req_combined_battle/battle_water") ||\r
                    url.EndsWith("api_req_combined_battle/midnight_battle") ||\r
-                   url.EndsWith("api_req_combined_battle/sp_midnight");\r
+                   url.EndsWith("api_req_combined_battle/sp_midnight") ||\r
+                   url.EndsWith("api_req_combined_battle/ec_battle") ||\r
+                   url.EndsWith("api_req_combined_battle/ec_midnight_battle");\r
         }\r
 \r
         private Update ApiOthers(string url, string request, dynamic data)\r