OSDN Git Service

連合艦隊時の僚艦夜戦突撃で第一旗艦に発動マークが付くのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / BattleInfo.cs
1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Linq;\r
18 using KancolleSniffer.Util;\r
19 using static System.Math;\r
20 \r
21 namespace KancolleSniffer.Model\r
22 {\r
23     public enum BattleResultRank\r
24     {\r
25         P,\r
26         S,\r
27         A,\r
28         B,\r
29         C,\r
30         D,\r
31         E\r
32     }\r
33 \r
34     public enum BattleState\r
35     {\r
36         None,\r
37         Day,\r
38         Night,\r
39         SpNight,\r
40         AirRaid,\r
41         Result,\r
42         Unknown\r
43     }\r
44 \r
45     public class EnemyFighterPower\r
46     {\r
47         public bool HasUnknown { get; set; }\r
48         public string UnknownMark => HasUnknown ? "+" : "";\r
49         public int AirCombat { get; set; }\r
50         public int Interception { get; set; }\r
51     }\r
52 \r
53     public class BattleInfo : Sniffer.IPort\r
54     {\r
55         private readonly ShipInfo _shipInfo;\r
56         private readonly ItemInfo _itemInfo;\r
57         private readonly AirBase _airBase;\r
58         private Fleet _fleet;\r
59         private Record[] _friend;\r
60         private Record[] _guard;\r
61         private Record[] _enemy;\r
62         private Record[] _enemyGuard;\r
63         private readonly List<int> _escapingShips = new List<int>();\r
64         private bool _safeCell;\r
65 \r
66         public BattleState BattleState { get; set; }\r
67         public int[] Formation { get; private set; }\r
68         public Range FighterPower { get; private set; }\r
69         public EnemyFighterPower EnemyFighterPower { get; private set; }\r
70         public int AirControlLevel { get; private set; }\r
71         public BattleResultRank ResultRank { get; private set; }\r
72         public RankPair DisplayedResultRank { get; } = new RankPair();\r
73         public BattleResult Result { get; private set; }\r
74         public bool EnemyIsCombined => _enemyGuard.Length > 0;\r
75         public AirBattleResult AirBattleResult { get; }\r
76         public int SupportType { get; private set; }\r
77 \r
78         public class RankPair\r
79         {\r
80             public char Assumed { get; set; } = 'X';\r
81             public char Actual { get; set; }\r
82             public bool IsError => Assumed != Actual;\r
83         }\r
84 \r
85         public class BattleResult\r
86         {\r
87             public class Combined\r
88             {\r
89                 public ShipStatus[] Main { get; set; }\r
90                 public ShipStatus[] Guard { get; set; }\r
91             }\r
92 \r
93             public Combined Friend { get; set; }\r
94             public Combined Enemy { get; set; }\r
95         }\r
96 \r
97         public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo, AirBase airBase)\r
98         {\r
99             _shipInfo = shipInfo;\r
100             _itemInfo = itemInfo;\r
101             _airBase = airBase;\r
102             AirBattleResult = new AirBattleResult(GetAirFireShipName, GetItemNames);\r
103         }\r
104 \r
105         private string GetAirFireShipName(int idx)\r
106         {\r
107             return idx < _friend.Length ? _friend[idx].Name : _guard[idx - 6].Name;\r
108         }\r
109 \r
110         private string[] GetItemNames(int[] ids)\r
111         {\r
112             return ids.Select(id => _itemInfo.GetSpecByItemId(id).Name).ToArray();\r
113         }\r
114 \r
115         public void Port()\r
116         {\r
117             CleanupResult();\r
118             _safeCell = false;\r
119             BattleState = BattleState.None;\r
120         }\r
121 \r
122         public void InspectBattle(string url, string request, dynamic json)\r
123         {\r
124             SetFormation(json);\r
125             SetSupportType(json);\r
126             ClearDamagedShipWarning();\r
127             ShowResult(); // 昼戦の結果を夜戦のときに表示する\r
128             SetupDamageRecord(request, json, url.Contains("practice"));\r
129             SetFighterPower();\r
130             SetEnemyFighterPower();\r
131             BattleState = url.Contains("sp_midnight") ? BattleState.SpNight :\r
132                 url.Contains("midnight") ? BattleState.Night : BattleState.Day;\r
133             if (BattleState != BattleState.Night)\r
134             {\r
135                 AirBattleResult.Clear();\r
136                 SetAirControlLevel(json);\r
137             }\r
138             CalcDamage(json);\r
139             ResultRank = url.Contains("/ld_") ? CalcLdResultRank() : CalcResultRank();\r
140             SetResult();\r
141         }\r
142 \r
143         private void SetFormation(dynamic json)\r
144         {\r
145             if (json.api_formation())\r
146                 Formation = (int[])json.api_formation;\r
147         }\r
148 \r
149         private void SetAirControlLevel(dynamic json)\r
150         {\r
151             AirControlLevel = -1;\r
152             if (!json.api_kouku())\r
153                 return;\r
154             var stage1 = json.api_kouku.api_stage1;\r
155             if (stage1 == null || stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
156                 return;\r
157             AirControlLevel = (int)stage1.api_disp_seiku;\r
158         }\r
159 \r
160         private void SetSupportType(dynamic json)\r
161         {\r
162             SupportType = json.api_support_flag() ? (int)json.api_support_flag :\r
163                 json.api_n_support_flag() ? (int)json.api_n_support_flag : 0;\r
164         }\r
165 \r
166         private void SetupDamageRecord(string request, dynamic json, bool practice)\r
167         {\r
168             if (_friend != null)\r
169                 return;\r
170             _shipInfo.SaveBattleStartStatus();\r
171             SetupFriendDamageRecord(request, json, practice);\r
172             SetupEnemyDamageRecord(json, practice);\r
173         }\r
174 \r
175         private void SetupFriendDamageRecord(string request, dynamic json, bool practice)\r
176         {\r
177             _fleet = _shipInfo.Fleets[(int)json.api_deck_id - 1];\r
178             FlagshipRecovery(request, _fleet.ActualShips[0]);\r
179             _friend = Record.Setup(_fleet.ActualShips, practice);\r
180             _guard = json.api_f_nowhps_combined()\r
181                 ? Record.Setup(_shipInfo.Fleets[1].ActualShips, practice)\r
182                 : new Record[0];\r
183             SetEscapedFlag(json);\r
184         }\r
185 \r
186         /// <summary>\r
187         /// EscapedはShipStatusにあるがBattleBriefTestの用のログにはShipStatusがないので、\r
188         /// ここで戦闘用のAPIを元に設定する。\r
189         /// </summary>\r
190         private void SetEscapedFlag(dynamic json)\r
191         {\r
192             if (json.api_escape_idx())\r
193             {\r
194                 foreach (int idx in json.api_escape_idx)\r
195                     _friend[idx - 1].Escaped = true;\r
196             }\r
197             if (json.api_escape_idx_combined())\r
198             {\r
199                 foreach (int idx in json.api_escape_idx_combined)\r
200                     _guard[idx - 1].Escaped = true;\r
201             }\r
202         }\r
203 \r
204         private void SetupEnemyDamageRecord(dynamic json, bool practice)\r
205         {\r
206             _enemy = Record.Setup((int[])json.api_e_nowhps,\r
207                 EnemyShipSpecs(json.api_ship_ke),\r
208                 EnemySlots(json.api_eSlot), practice);\r
209             _enemyGuard = json.api_ship_ke_combined()\r
210                 ? Record.Setup((int[])json.api_e_nowhps_combined,\r
211                     EnemyShipSpecs(json.api_ship_ke_combined),\r
212                     EnemySlots(json.api_eSlot_combined), practice)\r
213                 : new Record[0];\r
214         }\r
215 \r
216         private ShipSpec[] EnemyShipSpecs(dynamic ships)\r
217         {\r
218             return ((int[])ships).Select(_shipInfo.GetSpec).ToArray();\r
219         }\r
220 \r
221         private ItemSpec[][] EnemySlots(dynamic slots)\r
222         {\r
223             return ((int[][])slots).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray();\r
224         }\r
225 \r
226         private void SetResult()\r
227         {\r
228             Result = new BattleResult\r
229             {\r
230                 Friend = new BattleResult.Combined\r
231                 {\r
232                     Main = _friend.Select(r => r.SnapShot).ToArray(),\r
233                     Guard = _guard.Select(r => r.SnapShot).ToArray()\r
234                 },\r
235                 Enemy = new BattleResult.Combined\r
236                 {\r
237                     Main = _enemy.Select(r => r.SnapShot).ToArray(),\r
238                     Guard = _enemyGuard.Select(r => r.SnapShot).ToArray()\r
239                 }\r
240             };\r
241         }\r
242 \r
243         private void FlagshipRecovery(string request, ShipStatus flagship)\r
244         {\r
245             var type = int.Parse(HttpUtility.ParseQueryString(request)["api_recovery_type"] ?? "0");\r
246             switch (type)\r
247             {\r
248                 case 0:\r
249                     return;\r
250                 case 1:\r
251                     flagship.NowHp = flagship.MaxHp / 2;\r
252                     ConsumeSlotItem(flagship, 42); // ダメコン\r
253                     break;\r
254                 case 2:\r
255                     flagship.NowHp = flagship.MaxHp;\r
256                     ConsumeSlotItem(flagship, 43); // 女神\r
257                     break;\r
258             }\r
259             if (type != 0)\r
260                 _shipInfo.SetBadlyDamagedShips();\r
261         }\r
262 \r
263         private static void ConsumeSlotItem(ShipStatus ship, int id)\r
264         {\r
265             if (ship.SlotEx.Spec.Id == id)\r
266             {\r
267                 ship.SlotEx = new ItemStatus();\r
268                 return;\r
269             }\r
270             for (var i = 0; i < ship.Slot.Count; i++)\r
271             {\r
272                 if (ship.Slot[i].Spec.Id == id)\r
273                 {\r
274                     ship.FreeSlot(i);\r
275                     break;\r
276                 }\r
277             }\r
278         }\r
279 \r
280         private void CleanupResult()\r
281         {\r
282             _friend = null;\r
283         }\r
284 \r
285         private void SetFighterPower()\r
286         {\r
287             var fleets = _shipInfo.Fleets;\r
288             FighterPower = _guard.Length > 0 && _enemyGuard.Length > 0\r
289                 ? fleets[0].FighterPower + fleets[1].FighterPower\r
290                 : _fleet.FighterPower;\r
291         }\r
292 \r
293         private void SetEnemyFighterPower()\r
294         {\r
295             EnemyFighterPower = new EnemyFighterPower();\r
296             foreach (var record in _guard.Length == 0 ? _enemy : _enemy.Concat(_enemyGuard))\r
297             {\r
298                 var ship = record.SnapShot;\r
299                 if (ship.Spec.MaxEq == null)\r
300                 {\r
301                     EnemyFighterPower.HasUnknown = true;\r
302                     continue;\r
303                 }\r
304                 foreach (var entry in ship.Slot.Zip(ship.Spec.MaxEq, (item, maxEq) => new {item.Spec, maxEq}))\r
305                 {\r
306                     var perSlot = (int)Floor(entry.Spec.AntiAir * Sqrt(entry.maxEq));\r
307                     if (entry.Spec.CanAirCombat)\r
308                         EnemyFighterPower.AirCombat += perSlot;\r
309                     if (entry.Spec.IsAircraft)\r
310                         EnemyFighterPower.Interception += perSlot;\r
311                 }\r
312             }\r
313         }\r
314 \r
315         public void InspectMapStart(dynamic json)\r
316         {\r
317             InspectMapNext(json);\r
318         }\r
319 \r
320         public void InspectMapNext(dynamic json)\r
321         {\r
322             SetSafeCell(json);\r
323             BattleState = BattleState.None;\r
324             if (!json.api_destruction_battle())\r
325                 return;\r
326             InspectAirRaidBattle((int)json.api_maparea_id, json.api_destruction_battle);\r
327         }\r
328 \r
329         private void SetSafeCell(dynamic json)\r
330         {\r
331             var map = (int)json.api_maparea_id * 1000 + (int)json.api_mapinfo_no * 100 + (int)json.api_no;\r
332             _safeCell =\r
333                 (int)json.api_next == 0 || // last cell\r
334                 map switch\r
335                 {\r
336                     1613 => true, // 1-6-B\r
337                     1611 => true, // 1-6-D\r
338                     1616 => true, // 1-6-D\r
339                     2202 => true, // 2-2-B\r
340                     3102 => true, // 3-1-B\r
341                     3201 => true, // 3-2-A\r
342                     4206 => true, // 4-2-F\r
343                     5302 => true, // 5-3-B\r
344                     _ => false\r
345                 };\r
346         }\r
347 \r
348         private void InspectAirRaidBattle(int areaId, dynamic json)\r
349         {\r
350             SetFormation(json);\r
351             var attack = json.api_air_base_attack;\r
352             var stage1 = attack.api_stage1;\r
353             AirControlLevel = (int)stage1.api_disp_seiku;\r
354             var ships = (ShipStatus[])CreateShipsForAirBase(json);\r
355             _friend = Record.Setup(ships, false);\r
356             _guard = new Record[0];\r
357             FighterPower = _airBase.GetAirBase(areaId).CalcInterceptionFighterPower();\r
358             SetupEnemyDamageRecord(json, false);\r
359             SetEnemyFighterPower();\r
360             BattleState = BattleState.AirRaid;\r
361             AirBattleResult.Clear();\r
362             AirBattleResult.Add(json.api_air_base_attack, "空襲");\r
363             CalcKoukuDamage(json.api_air_base_attack);\r
364             SetAirRaidResultRank(json);\r
365             SetResult();\r
366             CleanupResult();\r
367         }\r
368 \r
369         private ShipStatus[] CreateShipsForAirBase(dynamic json)\r
370         {\r
371             var nowHps = (int[])json.api_f_nowhps;\r
372             var maxHps = (int[])json.api_f_maxhps;\r
373             var maxEq = new[] {18, 18, 18, 18};\r
374             var ships = nowHps.Select((hp, n) => new ShipStatus\r
375             {\r
376                 Id = 1,\r
377                 Spec = new ShipSpec {Name = "基地航空隊" + (n + 1), GetMaxEq = () => maxEq},\r
378                 NowHp = nowHps[n],\r
379                 MaxHp = maxHps[n]\r
380             }).ToArray();\r
381             var planes = json.api_air_base_attack.api_map_squadron_plane;\r
382             if (planes == null)\r
383                 return ships;\r
384             foreach (KeyValuePair<string, dynamic> entry in planes)\r
385             {\r
386                 var num = int.Parse(entry.Key) - 1;\r
387                 var slot = new List<ItemStatus>();\r
388                 var onSlot = new List<int>();\r
389                 foreach (var plane in entry.Value)\r
390                 {\r
391                     slot.Add(new ItemStatus {Id = 1, Spec = _itemInfo.GetSpecByItemId((int)plane.api_mst_id)});\r
392                     onSlot.Add((int)plane.api_count);\r
393                 }\r
394                 ships[num].Slot = slot;\r
395                 ships[num].OnSlot = onSlot.ToArray();\r
396             }\r
397             return ships;\r
398         }\r
399 \r
400         private void SetAirRaidResultRank(dynamic json)\r
401         {\r
402             switch ((int)json.api_lost_kind)\r
403             {\r
404                 case 1:\r
405                     ResultRank = BattleResultRank.A;\r
406                     break;\r
407                 case 2:\r
408                     ResultRank = BattleResultRank.B;\r
409                     break;\r
410                 case 3:\r
411                     ResultRank = BattleResultRank.C;\r
412                     break;\r
413                 case 4:\r
414                     ResultRank = BattleResultRank.S;\r
415                     break;\r
416             }\r
417         }\r
418 \r
419         private void CalcDamage(dynamic json)\r
420         {\r
421             foreach (KeyValuePair<string, dynamic> kv in json)\r
422             {\r
423                 if (kv.Value == null)\r
424                     continue;\r
425                 switch (kv.Key)\r
426                 {\r
427                     case "api_air_base_injection":\r
428                         AirBattleResult.Add(kv.Value, "AB噴式");\r
429                         CalcKoukuDamage(kv.Value);\r
430                         break;\r
431                     case "api_injection_kouku":\r
432                         AirBattleResult.Add(kv.Value, "噴式");\r
433                         CalcKoukuDamage(kv.Value);\r
434                         break;\r
435                     case "api_air_base_attack":\r
436                         CalcAirBaseAttackDamage(kv.Value);\r
437                         break;\r
438                     case "api_n_support_info":\r
439                         CalcSupportDamage(kv.Value);\r
440                         break;\r
441                     case "api_n_hougeki1":\r
442                         CalcDamageByTurn(kv.Value);\r
443                         break;\r
444                     case "api_n_hougeki2":\r
445                         CalcDamageByTurn(kv.Value);\r
446                         break;\r
447                     case "api_kouku":\r
448                         AirBattleResult.Add(kv.Value, "航空戦");\r
449                         CalcKoukuDamage(kv.Value);\r
450                         break;\r
451                     case "api_kouku2":\r
452                         AirBattleResult.Add(kv.Value, "航空戦2");\r
453                         CalcKoukuDamage(kv.Value);\r
454                         break;\r
455                     case "api_support_info":\r
456                         CalcSupportDamage(kv.Value);\r
457                         break;\r
458                     case "api_opening_taisen":\r
459                         CalcDamageByTurn(kv.Value);\r
460                         break;\r
461                     case "api_opening_atack":\r
462                         CalcDamageAtOnce(kv.Value);\r
463                         break;\r
464                     case "api_friendly_battle":\r
465                         CalcFriendAttackDamage(kv.Value);\r
466                         break;\r
467                     case "api_hougeki":\r
468                         CalcDamageByTurn(kv.Value);\r
469                         break;\r
470                     case "api_hougeki1":\r
471                         CalcDamageByTurn(kv.Value);\r
472                         break;\r
473                     case "api_hougeki2":\r
474                         CalcDamageByTurn(kv.Value);\r
475                         break;\r
476                     case "api_hougeki3":\r
477                         CalcDamageByTurn(kv.Value);\r
478                         break;\r
479                     case "api_raigeki":\r
480                         CalcDamageAtOnce(kv.Value);\r
481                         break;\r
482                 }\r
483             }\r
484         }\r
485 \r
486         private void CalcSupportDamage(dynamic json)\r
487         {\r
488             if (json.api_support_hourai != null)\r
489             {\r
490                 CalcRawDamageAtOnce(json.api_support_hourai.api_damage, _enemy, _enemyGuard);\r
491             }\r
492             else if (json.api_support_airatack != null)\r
493             {\r
494                 CalcRawDamageAtOnce(json.api_support_airatack.api_stage3.api_edam, _enemy, _enemyGuard);\r
495             }\r
496         }\r
497 \r
498         private void CalcAirBaseAttackDamage(dynamic json)\r
499         {\r
500             var i = 1;\r
501             foreach (var entry in json)\r
502             {\r
503                 AirBattleResult.Add(entry, "基地" + i++);\r
504                 CalcKoukuDamage(entry);\r
505             }\r
506         }\r
507 \r
508         private void CalcFriendAttackDamage(dynamic json)\r
509         {\r
510             CalcDamageByTurn(json.api_hougeki, true);\r
511         }\r
512 \r
513         private void CalcKoukuDamage(dynamic json)\r
514         {\r
515             if (json.api_stage3() && json.api_stage3 != null)\r
516                 CalcDamageAtOnce(json.api_stage3, _friend, _enemy);\r
517             if (json.api_stage3_combined() && json.api_stage3_combined != null)\r
518                 CalcDamageAtOnce(json.api_stage3_combined, _guard, _enemyGuard);\r
519         }\r
520 \r
521         private void CalcDamageAtOnce(dynamic json)\r
522         {\r
523             CalcDamageAtOnce(json, _friend, _guard, _enemy, _enemyGuard);\r
524         }\r
525 \r
526         private void CalcDamageAtOnce(dynamic json, Record[] friend, Record[] enemy)\r
527         {\r
528             CalcDamageAtOnce(json, friend, null, enemy, null);\r
529         }\r
530 \r
531         private void CalcDamageAtOnce(dynamic json,\r
532             Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
533         {\r
534             if (json.api_fdam() && json.api_fdam != null)\r
535                 CalcRawDamageAtOnce(json.api_fdam, friend, guard);\r
536             if (json.api_edam() && json.api_edam != null)\r
537                 CalcRawDamageAtOnce(json.api_edam, enemy, enemyGuard);\r
538         }\r
539 \r
540         private void CalcRawDamageAtOnce(dynamic rawDamage, Record[] friend, Record[] guard = null)\r
541         {\r
542             var damage = (int[])rawDamage;\r
543             for (var i = 0; i < friend.Length; i++)\r
544             {\r
545                 friend[i].ApplyDamage(damage[i]);\r
546                 friend[i].CheckDamageControl();\r
547             }\r
548             if (guard == null)\r
549                 return;\r
550             for (var i = 0; i < guard.Length; i++)\r
551             {\r
552                 guard[i].ApplyDamage(damage[i + 6]);\r
553                 guard[i].CheckDamageControl();\r
554             }\r
555         }\r
556 \r
557         private void CalcDamageByTurn(dynamic json, bool ignoreFriendDamage = false)\r
558         {\r
559             if (!(json.api_df_list() && json.api_df_list != null &&\r
560                   json.api_damage() && json.api_damage != null &&\r
561                   json.api_at_eflag() && json.api_at_eflag != null))\r
562                 return;\r
563 \r
564             var eFlags = (int[])json.api_at_eflag;\r
565             var night = json.api_sp_list();\r
566             var types = night ? (int[])json.api_sp_list : (int[])json.api_at_type;\r
567             var targets = (int[][])json.api_df_list;\r
568             var damages = (int[][])json.api_damage;\r
569             var records = new BothRecord(_friend, _guard, _enemy, _enemyGuard);\r
570             for (var turn = 0; turn < eFlags.Length; turn++)\r
571             {\r
572                 if (ignoreFriendDamage && eFlags[turn] == 1)\r
573                     continue;\r
574                 if (IsSpecialAttack(types[turn]))\r
575                 {\r
576                     var pos = night && _guard.Length > 0 ? 6 : 0; // 連合第二の僚艦夜戦突撃は6\r
577                     records.TriggerSpecialAttack(1, pos);\r
578                 }\r
579                 for (var shot = 0; shot < targets[turn].Length; shot++)\r
580                 {\r
581                     var target = targets[turn][shot];\r
582                     var damage = damages[turn][shot];\r
583                     if (target == -1 || damage == -1)\r
584                         continue;\r
585                     records.ApplyDamage(eFlags[turn], target, damage);\r
586                 }\r
587                 records.CheckDamageControl();\r
588             }\r
589         }\r
590 \r
591         private bool IsSpecialAttack(int type)\r
592         {\r
593             // 100: Nelson Touch\r
594             // 101: 長門一斉射\r
595             // 102: 陸奥一斉射\r
596             // 104: 金剛僚艦夜戦突撃\r
597             // 200: 瑞雲一体攻撃\r
598             // 201: 海陸立体攻撃\r
599             return type >= 100 && type < 200;\r
600         }\r
601 \r
602         private class BothRecord\r
603         {\r
604             private readonly Record[][] _records;\r
605 \r
606             public BothRecord(Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
607             {\r
608                 _records = new[] {new Record[12], new Record[12]};\r
609                 Array.Copy(friend, _records[1], friend.Length);\r
610                 Array.Copy(guard, 0, _records[1], 6, guard.Length);\r
611                 Array.Copy(enemy, _records[0], enemy.Length);\r
612                 Array.Copy(enemyGuard, 0, _records[0], 6, enemyGuard.Length);\r
613             }\r
614 \r
615             public void TriggerSpecialAttack(int side, int index)\r
616             {\r
617                 _records[side][index].TriggerSpecialAttack();\r
618             }\r
619 \r
620             public void ApplyDamage(int side, int index, int damage)\r
621             {\r
622                 _records[side][index].ApplyDamage(damage);\r
623             }\r
624 \r
625             public void CheckDamageControl()\r
626             {\r
627                 foreach (var ship in _records[1])\r
628                     ship?.CheckDamageControl();\r
629             }\r
630         }\r
631 \r
632         public void InspectBattleResult(dynamic json)\r
633         {\r
634             BattleState = BattleState.Result;\r
635             if (_friend == null)\r
636                 return;\r
637             ShowResult();\r
638             SetDamagedShipWarning();\r
639             _shipInfo.SaveBattleResult();\r
640             _shipInfo.DropShipId = json.api_get_ship() ? (int)json.api_get_ship.api_ship_id : -1;\r
641             VerifyResultRank(json);\r
642             CleanupResult();\r
643             SetEscapeShips(json);\r
644         }\r
645 \r
646         public void InspectPracticeResult(dynamic json)\r
647         {\r
648             BattleState = BattleState.Result;\r
649             if (_friend == null)\r
650                 return;\r
651             ShowResult();\r
652             VerifyResultRank(json);\r
653             CleanupResult();\r
654         }\r
655 \r
656         private void ShowResult()\r
657         {\r
658             if (_friend == null)\r
659                 return;\r
660             var fleets = _shipInfo.Fleets;\r
661             var ships = _guard.Length > 0\r
662                 ? fleets[0].ActualShips.Concat(fleets[1].ActualShips)\r
663                 : _fleet.ActualShips;\r
664             foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
665                 entry.now.UpdateShipStatus(entry.ship);\r
666         }\r
667 \r
668         private void SetDamagedShipWarning()\r
669         {\r
670             if (_safeCell)\r
671                 return;\r
672             _shipInfo.SetBadlyDamagedShips();\r
673         }\r
674 \r
675         private void ClearDamagedShipWarning()\r
676         {\r
677             _shipInfo.ClearBadlyDamagedShips();\r
678         }\r
679 \r
680         private void VerifyResultRank(dynamic json)\r
681         {\r
682             if (!json.api_win_rank())\r
683                 return;\r
684             var assumed = "PSABCDE"[(int)ResultRank];\r
685             if (assumed == 'P')\r
686                 assumed = 'S';\r
687             var actual = ((string)json.api_win_rank)[0];\r
688             DisplayedResultRank.Assumed = assumed;\r
689             DisplayedResultRank.Actual = actual;\r
690         }\r
691 \r
692         private void SetEscapeShips(dynamic json)\r
693         {\r
694             _escapingShips.Clear();\r
695             if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
696                 return;\r
697             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
698             if (json.api_escape.api_tow_idx())\r
699             {\r
700                 _escapingShips.Add(_shipInfo.Fleets[damaged / 6].Deck[damaged % 6]);\r
701                 var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
702                 _escapingShips.Add(_shipInfo.Fleets[escort / 6].Deck[escort % 6]);\r
703             }\r
704             else\r
705             {\r
706                 _escapingShips.Add(_shipInfo.Fleets[2].Deck[damaged]);\r
707             }\r
708         }\r
709 \r
710         public void CauseEscape()\r
711         {\r
712             _shipInfo.SetEscapedShips(_escapingShips);\r
713             _shipInfo.SetBadlyDamagedShips();\r
714         }\r
715 \r
716         private class Record\r
717         {\r
718             private ShipStatus _status;\r
719             private bool _practice;\r
720             public ShipStatus SnapShot => (ShipStatus)_status.Clone();\r
721             public int NowHp => _status.NowHp;\r
722             public bool Escaped { get; set; }\r
723             public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
724             public string Name => _status.Name;\r
725             public int StartHp { get; private set; }\r
726 \r
727             public static Record[] Setup(IEnumerable<ShipStatus> ships, bool practice) =>\r
728                 (from s in ships\r
729                     select new Record {_status = (ShipStatus)s.Clone(), _practice = practice, StartHp = s.NowHp})\r
730                 .ToArray();\r
731 \r
732             public static Record[] Setup(int[] nowHps, ShipSpec[] specs, ItemSpec[][] slots, bool practice)\r
733             {\r
734                 return Enumerable.Range(0, nowHps.Length).Select(i =>\r
735                     new Record\r
736                     {\r
737                         StartHp = nowHps[i],\r
738                         _status = new ShipStatus\r
739                         {\r
740                             Id = specs[i].Id,\r
741                             NowHp = nowHps[i],\r
742                             MaxHp = nowHps[i],\r
743                             Spec = specs[i],\r
744                             Slot = slots[i].Select(spec => new ItemStatus {Id = spec.Id, Spec = spec}).ToArray(),\r
745                             SlotEx = new ItemStatus(0)\r
746                         },\r
747                         _practice = practice\r
748                     }).ToArray();\r
749             }\r
750 \r
751             public void TriggerSpecialAttack()\r
752             {\r
753                 _status.SpecialAttack = ShipStatus.Attack.Fire;\r
754             }\r
755 \r
756             public void ApplyDamage(int damage)\r
757             {\r
758                 _status.NowHp = Max(0, _status.NowHp - damage);\r
759             }\r
760 \r
761             public void CheckDamageControl()\r
762             {\r
763                 if (_status.NowHp > 0 || _practice)\r
764                     return;\r
765                 foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))\r
766                 {\r
767                     if (item.Spec.Id == 42)\r
768                     {\r
769                         _status.NowHp = (int)(_status.MaxHp * 0.2);\r
770                         ConsumeSlotItem(_status, 42);\r
771                         break;\r
772                     }\r
773                     if (item.Spec.Id == 43)\r
774                     {\r
775                         _status.NowHp = _status.MaxHp;\r
776                         ConsumeSlotItem(_status, 43);\r
777                         break;\r
778                     }\r
779                 }\r
780             }\r
781 \r
782             public void UpdateShipStatus(ShipStatus ship)\r
783             {\r
784                 ship.NowHp = NowHp;\r
785                 ship.Slot = _status.Slot;\r
786                 ship.SlotEx = _status.SlotEx;\r
787                 if (_status.SpecialAttack == ShipStatus.Attack.Fire)\r
788                     ship.SpecialAttack = ShipStatus.Attack.Fired;\r
789             }\r
790         }\r
791 \r
792         private BattleResultRank CalcLdResultRank()\r
793         {\r
794             var friend = new ResultRankParams(_friend.Concat(_guard).ToArray());\r
795 \r
796             if (friend.Gauge <= 0)\r
797                 return BattleResultRank.P;\r
798             if (friend.GaugeRate < 10)\r
799                 return BattleResultRank.A;\r
800             if (friend.GaugeRate < 20)\r
801                 return BattleResultRank.B;\r
802             if (friend.GaugeRate < 50)\r
803                 return BattleResultRank.C;\r
804             if (friend.GaugeRate < 80)\r
805                 return BattleResultRank.D;\r
806             return BattleResultRank.E;\r
807         }\r
808 \r
809         private BattleResultRank CalcResultRank()\r
810         {\r
811             var friend = new ResultRankParams(_friend.Concat(_guard).ToArray());\r
812             var enemy = new ResultRankParams(_enemy.Concat(_enemyGuard).ToArray());\r
813             if (friend.Sunk == 0 && enemy.Sunk == enemy.Count)\r
814             {\r
815                 if (friend.Gauge <= 0)\r
816                     return BattleResultRank.P;\r
817                 return BattleResultRank.S;\r
818             }\r
819             if (friend.Sunk == 0 && enemy.Sunk >= (int)(enemy.Count * 0.7) && enemy.Count > 1)\r
820                 return BattleResultRank.A;\r
821             if (friend.Sunk < enemy.Sunk && _enemy[0].NowHp == 0)\r
822                 return BattleResultRank.B;\r
823             if (friend.Count == 1 && _friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
824                 return BattleResultRank.D;\r
825             if (enemy.GaugeRate > friend.GaugeRate * 2.5)\r
826                 return BattleResultRank.B;\r
827             if (enemy.GaugeRate > friend.GaugeRate * 0.9)\r
828                 return BattleResultRank.C;\r
829             if (friend.Count > 1 && friend.Count - 1 == friend.Sunk)\r
830                 return BattleResultRank.E;\r
831             return BattleResultRank.D;\r
832         }\r
833 \r
834         private class ResultRankParams\r
835         {\r
836             public readonly int Count;\r
837             public readonly int Sunk;\r
838             public readonly int Gauge;\r
839             public readonly int GaugeRate;\r
840 \r
841             public ResultRankParams(Record[] records)\r
842             {\r
843                 Count = records.Length;\r
844                 Sunk = records.Count(r => r.NowHp == 0);\r
845                 Gauge = records.Sum(r => r.StartHp - r.NowHp);\r
846                 GaugeRate = (int)((double)Gauge / records.Sum(r => r.Escaped ? 0 : r.StartHp) * 100);\r
847             }\r
848         }\r
849 \r
850         /// <summary>\r
851         /// テスト専用\r
852         /// </summary>\r
853         public void InjectResultStatus(ShipStatus[] main, ShipStatus[] guard, ShipStatus[] enemy,\r
854             ShipStatus[] enemyGuard)\r
855         {\r
856             Result = new BattleResult\r
857             {\r
858                 Friend = new BattleResult.Combined {Main = main, Guard = guard},\r
859                 Enemy = new BattleResult.Combined {Main = enemy, Guard = enemyGuard}\r
860             };\r
861         }\r
862     }\r
863 }